fixed merge conflict in globals.css
3
components/Loader.tsx
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default function Loader(){
|
||||
return <div className="loader"><span className="loader-inner"></span></div>
|
||||
}
|
7
components/Logo.tsx
Normal file
|
@ -0,0 +1,7 @@
|
|||
export default function Logo({small} : {small?: boolean}){
|
||||
return <h1 className="brand-logo inline">
|
||||
<strong>
|
||||
<img className={small ? "h-4 w-auto" : "h-5 w-auto"} alt="Calendso" title="Calendso" src="/calendso-logo-white-word.svg" />
|
||||
</strong>
|
||||
</h1>;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { Fragment, useState } from 'react'
|
||||
import { Fragment } from 'react'
|
||||
import { Dialog, Transition } from '@headlessui/react'
|
||||
import { CheckIcon } from '@heroicons/react/outline'
|
||||
|
||||
|
|
|
@ -1,87 +1,70 @@
|
|||
import ActiveLink from '../components/ActiveLink';
|
||||
import {CodeIcon, CreditCardIcon, KeyIcon, UserCircleIcon, UserGroupIcon} from '@heroicons/react/outline';
|
||||
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(" ");
|
||||
}
|
||||
|
||||
export default function SettingsShell(props) {
|
||||
return (
|
||||
<div>
|
||||
<main className="relative -mt-32">
|
||||
<div>
|
||||
<div className="bg-white rounded-lg shadow">
|
||||
<div className="divide-y divide-gray-200 lg:grid lg:grid-cols-12 lg:divide-y-0 lg:divide-x">
|
||||
<aside className="py-6 lg:col-span-3">
|
||||
<nav id="nav--settings" className="space-y-1">
|
||||
const router = useRouter();
|
||||
|
||||
<ActiveLink href="/settings/profile">
|
||||
<a><UserCircleIcon /> Profile</a>
|
||||
</ActiveLink>
|
||||
const tabs = [
|
||||
{
|
||||
name: "Profile",
|
||||
href: "/settings/profile",
|
||||
icon: UserIcon,
|
||||
current: router.pathname == "/settings/profile",
|
||||
},
|
||||
{
|
||||
name: "Password",
|
||||
href: "/settings/password",
|
||||
icon: KeyIcon,
|
||||
current: router.pathname == "/settings/password",
|
||||
},
|
||||
{ name: "Embed", href: "/settings/embed", icon: CodeIcon, current: router.pathname == "/settings/embed" },
|
||||
{
|
||||
name: "Teams",
|
||||
href: "/settings/teams",
|
||||
icon: UserGroupIcon,
|
||||
current: router.pathname == "/settings/teams",
|
||||
},
|
||||
{
|
||||
name: "Billing",
|
||||
href: "/settings/billing",
|
||||
icon: CreditCardIcon,
|
||||
current: router.pathname == "/settings/billing",
|
||||
},
|
||||
];
|
||||
|
||||
{/* <Link href="/settings/account">
|
||||
<a className={router.pathname == "/settings/account" ? "bg-blue-50 border-blue-500 text-blue-700 hover:bg-blue-50 hover:text-blue-700 group border-l-4 px-3 py-2 flex items-center text-sm font-medium" : "border-transparent text-gray-900 hover:bg-gray-50 hover:text-gray-900 group border-l-4 px-3 py-2 flex items-center text-sm font-medium"}>
|
||||
<svg className={router.pathname == "/settings/account" ? "text-blue-500 group-hover:text-blue-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6" : "text-gray-400 group-hover:text-gray-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6"} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
<span className="truncate">
|
||||
Account
|
||||
</span>
|
||||
</a>
|
||||
</Link> */}
|
||||
|
||||
<ActiveLink href="/settings/password">
|
||||
<a><KeyIcon /> Password</a>
|
||||
</ActiveLink>
|
||||
<ActiveLink href="/settings/embed">
|
||||
<a><CodeIcon /> Embed</a>
|
||||
</ActiveLink>
|
||||
<ActiveLink href="/settings/teams">
|
||||
<a><UserGroupIcon /> Teams</a>
|
||||
</ActiveLink>
|
||||
|
||||
{/* Change/remove me, if you're self-hosting */}
|
||||
<ActiveLink href="/settings/billing">
|
||||
<a><CreditCardIcon /> Billing</a>
|
||||
</ActiveLink>
|
||||
|
||||
{/* <Link href="/settings/notifications">
|
||||
<a className={router.pathname == "/settings/notifications" ? "bg-blue-50 border-blue-500 text-blue-700 hover:bg-blue-50 hover:text-blue-700 group border-l-4 px-3 py-2 flex items-center text-sm font-medium" : "border-transparent text-gray-900 hover:bg-gray-50 hover:text-gray-900 group border-l-4 px-3 py-2 flex items-center text-sm font-medium"}>
|
||||
<svg className={router.pathname == "/settings/notifications" ? "text-blue-500 group-hover:text-blue-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6" : "text-gray-400 group-hover:text-gray-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6"} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
||||
</svg>
|
||||
<span className="truncate">
|
||||
Notifications
|
||||
</span>
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
<Link href="/settings/billing">
|
||||
<a className={router.pathname == "/settings/billing" ? "bg-blue-50 border-blue-500 text-blue-700 hover:bg-blue-50 hover:text-blue-700 group border-l-4 px-3 py-2 flex items-center text-sm font-medium" : "border-transparent text-gray-900 hover:bg-gray-50 hover:text-gray-900 group border-l-4 px-3 py-2 flex items-center text-sm font-medium"}>
|
||||
<svg className={router.pathname == "/settings/billing" ? "text-blue-500 group-hover:text-blue-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6" : "text-gray-400 group-hover:text-gray-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6"} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" />
|
||||
</svg>
|
||||
<span className="truncate">
|
||||
Billing
|
||||
</span>
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
<Link href="/settings/integrations">
|
||||
<a className={router.pathname == "/settings/integrations" ? "bg-blue-50 border-blue-500 text-blue-700 hover:bg-blue-50 hover:text-blue-700 group border-l-4 px-3 py-2 flex items-center text-sm font-medium" : "border-transparent text-gray-900 hover:bg-gray-50 hover:text-gray-900 group border-l-4 px-3 py-2 flex items-center text-sm font-medium"}>
|
||||
<svg className={router.pathname == "/settings/integrations" ? "text-blue-500 group-hover:text-blue-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6" : "text-gray-400 group-hover:text-gray-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6"} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M17 14v6m-3-3h6M6 10h2a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v2a2 2 0 002 2zm10 0h2a2 2 0 002-2V6a2 2 0 00-2-2h-2a2 2 0 00-2 2v2a2 2 0 002 2zM6 20h2a2 2 0 002-2v-2a2 2 0 00-2-2H6a2 2 0 00-2 2v2a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<span className="truncate">
|
||||
Integrations
|
||||
</span>
|
||||
</a>
|
||||
</Link> */}
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="max-w-6xl">
|
||||
<div className="sm:mx-auto">
|
||||
<nav className="-mb-px flex space-x-2 sm:space-x-8" aria-label="Tabs">
|
||||
{tabs.map((tab) => (
|
||||
<Link key={tab.name} href={tab.href}>
|
||||
<a
|
||||
className={classNames(
|
||||
tab.current
|
||||
? "border-neutral-900 text-neutral-900"
|
||||
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300",
|
||||
"group inline-flex items-center py-4 px-1 border-b-2 font-medium text-sm"
|
||||
)}
|
||||
aria-current={tab.current ? "page" : undefined}>
|
||||
<tab.icon
|
||||
className={classNames(
|
||||
tab.current ? "text-neutral-900" : "text-gray-400 group-hover:text-gray-500",
|
||||
"-ml-0.5 mr-2 h-5 w-5 hidden sm:inline-block"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>{tab.name}</span>
|
||||
</a>
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
<main>{props.children}</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,277 +1,332 @@
|
|||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
import React, { Fragment, useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { signOut, useSession } from "next-auth/client";
|
||||
import { MenuIcon, XIcon } from "@heroicons/react/outline";
|
||||
import { useSession } from "next-auth/client";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "../lib/telemetry";
|
||||
import { SelectorIcon } from "@heroicons/react/outline";
|
||||
import {
|
||||
CalendarIcon,
|
||||
ClockIcon,
|
||||
PuzzleIcon,
|
||||
CogIcon,
|
||||
ChatAltIcon,
|
||||
LogoutIcon,
|
||||
ExternalLinkIcon,
|
||||
LinkIcon,
|
||||
} from "@heroicons/react/solid";
|
||||
import Logo from "./Logo";
|
||||
|
||||
function classNames(...classes) {
|
||||
return classes.filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
export default function Shell(props) {
|
||||
const router = useRouter();
|
||||
const [session, loading] = useSession();
|
||||
const [profileDropdownExpanded, setProfileDropdownExpanded] = useState(false);
|
||||
const [mobileMenuExpanded, setMobileMenuExpanded] = useState(false);
|
||||
const telemetry = useTelemetry();
|
||||
|
||||
const navigation = [
|
||||
{
|
||||
name: "Event Types",
|
||||
href: "/event-types",
|
||||
icon: LinkIcon,
|
||||
current: router.pathname.startsWith("/event-types"),
|
||||
},
|
||||
{
|
||||
name: "Bookings",
|
||||
href: "/bookings",
|
||||
icon: ClockIcon,
|
||||
current: router.pathname.startsWith("/bookings"),
|
||||
},
|
||||
{
|
||||
name: "Availability",
|
||||
href: "/availability",
|
||||
icon: CalendarIcon,
|
||||
current: router.pathname.startsWith("/availability"),
|
||||
},
|
||||
{
|
||||
name: "Integrations",
|
||||
href: "/integrations",
|
||||
icon: PuzzleIcon,
|
||||
current: router.pathname.startsWith("/integrations"),
|
||||
},
|
||||
{
|
||||
name: "Settings",
|
||||
href: "/settings/profile",
|
||||
icon: CogIcon,
|
||||
current: router.pathname.startsWith("/settings"),
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
telemetry.withJitsu((jitsu) => {
|
||||
return jitsu.track(telemetryEventTypes.pageView, collectPageParameters(router.pathname));
|
||||
});
|
||||
}, [telemetry]);
|
||||
|
||||
const toggleProfileDropdown = () => {
|
||||
setProfileDropdownExpanded(!profileDropdownExpanded);
|
||||
};
|
||||
|
||||
const toggleMobileMenu = () => {
|
||||
setMobileMenuExpanded(!mobileMenuExpanded);
|
||||
};
|
||||
|
||||
const logoutHandler = () => {
|
||||
signOut({ redirect: false }).then(() => router.push("/auth/logout"));
|
||||
};
|
||||
|
||||
if (!loading && !session) {
|
||||
router.replace("/auth/login");
|
||||
}
|
||||
|
||||
return session ? (
|
||||
<div>
|
||||
<div className="dark:from-gray-900 dark:to-gray-900 bg-gradient-to-b from-blue-600 via-blue-600 to-blue-300 pb-32">
|
||||
<nav className="dark:bg-gray-900 bg-blue-600">
|
||||
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div className="border-b border-blue-500 dark:border-gray-400">
|
||||
<div className="flex items-center justify-between h-16 px-4 sm:px-0">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0">
|
||||
<Link href="/">
|
||||
<a>
|
||||
<img className="h-6" src="/calendso-white.svg" alt="Calendso" />
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="hidden md:block">
|
||||
<div className="ml-10 flex items-baseline space-x-4">
|
||||
<Link href="/">
|
||||
<a
|
||||
className={
|
||||
router.pathname == "/"
|
||||
? "bg-blue-500 transition-colors duration-300 ease-in-out text-white px-3 py-2 rounded-md text-sm font-medium"
|
||||
: "text-white hover:bg-blue-500 transition-colors duration-300 ease-in-out hover:text-white px-3 py-2 rounded-md text-sm font-medium"
|
||||
}>
|
||||
Dashboard
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="/bookings">
|
||||
<a
|
||||
className={
|
||||
router.pathname.startsWith("/bookings")
|
||||
? "bg-blue-500 transition-colors duration-300 ease-in-out text-white px-3 py-2 rounded-md text-sm font-medium"
|
||||
: "text-white hover:bg-blue-500 transition-colors duration-300 ease-in-out hover:text-white px-3 py-2 rounded-md text-sm font-medium"
|
||||
}>
|
||||
Bookings
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="/availability">
|
||||
<a
|
||||
className={
|
||||
router.pathname.startsWith("/availability")
|
||||
? "bg-blue-500 transition-colors duration-300 ease-in-out text-white px-3 py-2 rounded-md text-sm font-medium"
|
||||
: "text-white hover:bg-blue-500 transition-colors duration-300 ease-in-out hover:text-white px-3 py-2 rounded-md text-sm font-medium"
|
||||
}>
|
||||
Availability
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="/integrations">
|
||||
<a
|
||||
className={
|
||||
router.pathname.startsWith("/integrations")
|
||||
? "bg-blue-500 transition-colors duration-300 ease-in-out text-white px-3 py-2 rounded-md text-sm font-medium"
|
||||
: "text-white hover:bg-blue-500 transition-colors duration-300 ease-in-out hover:text-white px-3 py-2 rounded-md text-sm font-medium"
|
||||
}>
|
||||
Integrations
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="/settings/profile">
|
||||
<a
|
||||
className={
|
||||
router.pathname.startsWith("/settings")
|
||||
? "bg-blue-500 transition-colors duration-300 ease-in-out text-white px-3 py-2 rounded-md text-sm font-medium"
|
||||
: "text-white hover:bg-blue-500 transition-colors duration-300 ease-in-out hover:text-white px-3 py-2 rounded-md text-sm font-medium"
|
||||
}>
|
||||
Settings
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden md:block">
|
||||
<div className="ml-4 flex items-center md:ml-6">
|
||||
<div className="ml-3 relative">
|
||||
<div>
|
||||
<button
|
||||
onClick={toggleProfileDropdown}
|
||||
type="button"
|
||||
className="max-w-xs bg-gray-800 rounded-full flex items-center text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white"
|
||||
id="user-menu"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true">
|
||||
<span className="sr-only">Open user menu</span>
|
||||
<img
|
||||
className="h-8 w-8 rounded-full"
|
||||
src={
|
||||
session.user.image
|
||||
? session.user.image
|
||||
: "https://eu.ui-avatars.com/api/?background=fff&color=039be5&name=" +
|
||||
encodeURIComponent(session.user.name || "")
|
||||
}
|
||||
alt=""
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
{profileDropdownExpanded && (
|
||||
<div
|
||||
className="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none z-50"
|
||||
role="menu"
|
||||
aria-orientation="vertical"
|
||||
aria-labelledby="user-menu">
|
||||
<Link href={"/" + session.user.username}>
|
||||
<a
|
||||
target="_blank"
|
||||
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
||||
role="menuitem">
|
||||
Your Public Page
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="/settings/profile">
|
||||
<a
|
||||
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
||||
role="menuitem">
|
||||
Your Profile
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="/settings/password">
|
||||
<a
|
||||
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
||||
role="menuitem">
|
||||
Login & Security
|
||||
</a>
|
||||
</Link>
|
||||
<button
|
||||
onClick={logoutHandler}
|
||||
className="w-full text-left block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
||||
role="menuitem">
|
||||
Sign out
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="-mr-2 flex md:hidden">
|
||||
<button
|
||||
onClick={toggleMobileMenu}
|
||||
type="button"
|
||||
className=" inline-flex items-center justify-center p-2 rounded-md text-white focus:outline-none"
|
||||
aria-controls="mobile-menu"
|
||||
aria-expanded="false">
|
||||
<span className="sr-only">Open main menu</span>
|
||||
{!mobileMenuExpanded && <MenuIcon className="block h-6 w-6" />}
|
||||
{mobileMenuExpanded && <XIcon className="block h-6 w-6" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{mobileMenuExpanded && (
|
||||
<div className="border-b border-blue-500 md:hidden bg-blue-600" id="mobile-menu">
|
||||
<div className="px-2 py-3 space-y-1 sm:px-3">
|
||||
<Link href="/">
|
||||
<a
|
||||
className={
|
||||
router.pathname == "/"
|
||||
? "bg-blue-500 text-white block px-3 py-2 rounded-md text-base font-medium"
|
||||
: "text-gray-100 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium"
|
||||
}>
|
||||
Dashboard
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="/availability">
|
||||
<a
|
||||
className={
|
||||
router.pathname.startsWith("/availability")
|
||||
? "bg-blue-500 text-white block px-3 py-2 rounded-md text-base font-medium"
|
||||
: "text-gray-100 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium"
|
||||
}>
|
||||
Availability
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="/integrations">
|
||||
<a
|
||||
className={
|
||||
router.pathname.startsWith("/integrations")
|
||||
? "bg-blue-500 text-white block px-3 py-2 rounded-md text-base font-medium"
|
||||
: "text-gray-100 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium"
|
||||
}>
|
||||
Integrations
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="pt-4 pb-3 border-t border-blue-500">
|
||||
<div className="flex items-center px-5">
|
||||
<div className="flex-shrink-0">
|
||||
<img
|
||||
className="h-10 w-10 rounded-full"
|
||||
src={
|
||||
"https://eu.ui-avatars.com/api/?background=039be5&color=fff&name=" +
|
||||
encodeURIComponent(session.user.name || session.user.username)
|
||||
}
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<div className="text-base font-medium leading-none text-white">
|
||||
{session.user.name || session.user.username}
|
||||
</div>
|
||||
<div className="text-sm font-medium leading-none text-gray-200">{session.user.email}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 px-2 space-y-1">
|
||||
<Link href="/settings/profile">
|
||||
<a className="block px-3 py-2 rounded-md text-base font-medium text-gray-100 hover:text-white hover:bg-gray-700">
|
||||
Your Profile
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="/settings">
|
||||
<div className="h-screen flex overflow-hidden bg-gray-100">
|
||||
{/* Static sidebar for desktop */}
|
||||
<div className="hidden md:flex md:flex-shrink-0">
|
||||
<div className="flex flex-col w-64">
|
||||
{/* Sidebar component, swap this element with another sidebar if you like */}
|
||||
<div className="flex flex-col h-0 flex-1 border-r border-gray-200 bg-white">
|
||||
<div className="flex-1 flex flex-col pt-5 pb-4 overflow-y-auto">
|
||||
<Link href="/event-types">
|
||||
<a className="px-4">
|
||||
<Logo small />
|
||||
</a>
|
||||
</Link>
|
||||
<nav className="mt-5 flex-1 px-2 bg-white space-y-1">
|
||||
{navigation.map((item) => (
|
||||
<Link key={item.name} href={item.href}>
|
||||
<a
|
||||
className={
|
||||
router.pathname.startsWith("/settings")
|
||||
? "bg-blue-500 text-white block px-3 py-2 rounded-md text-base font-medium"
|
||||
: "text-gray-100 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium"
|
||||
}>
|
||||
Settings
|
||||
className={classNames(
|
||||
item.current
|
||||
? "bg-neutral-100 text-neutral-900"
|
||||
: "text-neutral-500 hover:bg-gray-50 hover:text-neutral-900",
|
||||
"group flex items-center px-2 py-2 text-sm font-medium rounded-sm"
|
||||
)}>
|
||||
<item.icon
|
||||
className={classNames(
|
||||
item.current ? "text-neutral-500" : "text-neutral-400 group-hover:text-neutral-500",
|
||||
"mr-3 flex-shrink-0 h-5 w-5"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{item.name}
|
||||
</a>
|
||||
</Link>
|
||||
<button
|
||||
onClick={logoutHandler}
|
||||
className="block w-full text-left px-3 py-2 rounded-md text-base font-medium text-gray-100 hover:text-white hover:bg-gray-700">
|
||||
Sign out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
<div className="flex-shrink-0 flex border-t border-gray-200 p-4">
|
||||
<UserDropdown session={session} />
|
||||
</div>
|
||||
)}
|
||||
</nav>
|
||||
<header className={props.noPaddingBottom ? "pt-10" : "py-10"}>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<h1 className="text-3xl font-bold text-white">{props.heading}</h1>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col w-0 flex-1 overflow-hidden">
|
||||
{/* show top navigation for md and smaller (tablet and phones) */}
|
||||
<nav className="md:hidden bg-white shadow p-4 flex justify-between items-center">
|
||||
<Link href="/event-types">
|
||||
<a>
|
||||
<Logo />
|
||||
</a>
|
||||
</Link>
|
||||
<div className="flex gap-3 items-center self-center">
|
||||
<button className="bg-white p-2 rounded-full text-gray-400 hover:text-gray-500 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black">
|
||||
<span className="sr-only">View notifications</span>
|
||||
<Link href="/settings/profile">
|
||||
<a>
|
||||
<CogIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</a>
|
||||
</Link>
|
||||
</button>
|
||||
<div className="mt-1">
|
||||
<UserDropdown small bottom session={session} />
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main className="-mt-32">
|
||||
<div className="max-w-7xl mx-auto pb-12 px-4 sm:px-6 lg:px-8">{props.children}</div>
|
||||
</main>
|
||||
<main className="flex-1 relative z-0 overflow-y-auto focus:outline-none">
|
||||
<div className="py-6">
|
||||
<div className="block sm:flex justify-between px-4 sm:px-6 md:px-8">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-semibold text-gray-900">{props.heading}</h1>
|
||||
<p className="text-sm text-neutral-500 mr-4">{props.subtitle}</p>
|
||||
</div>
|
||||
<div className="mb-4 flex-shrink-0">{props.CTA}</div>
|
||||
</div>
|
||||
<div className="px-4 sm:px-6 md:px-8">{props.children}</div>
|
||||
|
||||
{/* show bottom navigation for md and smaller (tablet and phones) */}
|
||||
<nav className="md:hidden flex fixed bottom-0 bg-white w-full rounded-lg shadow">
|
||||
{/* note(PeerRich): using flatMap instead of map to remove settings from bottom nav */}
|
||||
{navigation.flatMap((item, itemIdx) =>
|
||||
item.name === "Settings" ? (
|
||||
[]
|
||||
) : (
|
||||
<Link key={item.name} href={item.href}>
|
||||
<a
|
||||
className={classNames(
|
||||
item.current ? "text-gray-900" : "text-neutral-400 hover:text-gray-700",
|
||||
itemIdx === 0 ? "rounded-l-lg" : "",
|
||||
itemIdx === navigation.length - 1 ? "rounded-r-lg" : "",
|
||||
"group relative min-w-0 flex-1 overflow-hidden bg-white py-2 px-2 text-xs sm:text-sm font-medium text-center hover:bg-gray-50 focus:z-10"
|
||||
)}
|
||||
aria-current={item.current ? "page" : undefined}>
|
||||
<item.icon
|
||||
className={classNames(
|
||||
item.current ? "text-gray-900" : "text-gray-400 group-hover:text-gray-500",
|
||||
"block mx-auto flex-shrink-0 h-5 w-5 mb-1 text-center"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>{item.name}</span>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
)}
|
||||
</nav>
|
||||
|
||||
{/* add padding to content for mobile navigation*/}
|
||||
<div className="block md:hidden pt-12" />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
|
||||
function UserDropdown({ session, small, bottom }: { session: any; small?: boolean; bottom?: boolean }) {
|
||||
return (
|
||||
<Menu as="div" className="w-full relative inline-block text-left">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<div>
|
||||
<Menu.Button className="group w-full rounded-md text-sm text-left font-medium text-gray-700 focus:outline-none">
|
||||
<span className="flex w-full justify-between items-center">
|
||||
<span className="flex min-w-0 items-center justify-between space-x-3">
|
||||
<img
|
||||
className={classNames(
|
||||
small ? "w-8 h-8" : "w-10 h-10",
|
||||
"bg-gray-300 rounded-full flex-shrink-0"
|
||||
)}
|
||||
src={
|
||||
session.user.image
|
||||
? session.user.image
|
||||
: "https://eu.ui-avatars.com/api/?background=fff&color=039be5&name=" +
|
||||
encodeURIComponent(session.user.name || "")
|
||||
}
|
||||
alt=""
|
||||
/>
|
||||
{!small && (
|
||||
<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}
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
{!small && (
|
||||
<SelectorIcon
|
||||
className="flex-shrink-0 h-5 w-5 text-gray-400 group-hover:text-gray-500"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</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={classNames(
|
||||
bottom ? "origin-top top-1 right-0" : "origin-bottom bottom-14 left-0",
|
||||
"w-64 z-10 absolute mt-1 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 divide-y divide-gray-200 focus:outline-none"
|
||||
)}>
|
||||
<div className="py-1">
|
||||
<a href={"/" + session.user.username} className="flex px-4 py-2 text-sm text-neutral-500">
|
||||
View public page <ExternalLinkIcon className="ml-1 mt-1 w-3 h-3 text-neutral-400" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="py-1">
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<a
|
||||
href="https://calendso.com/slack"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={classNames(
|
||||
active ? "bg-gray-100 text-gray-900" : "text-neutral-700",
|
||||
"flex px-4 py-2 text-sm font-medium"
|
||||
)}>
|
||||
<svg
|
||||
viewBox="0 0 2447.6 2452.5"
|
||||
className={classNames(
|
||||
"text-neutral-400 group-hover:text-neutral-500",
|
||||
"mt-0.5 mr-3 flex-shrink-0 h-4 w-4"
|
||||
)}
|
||||
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="#9BA6B6"></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="#9BA6B6"></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="#9BA6B6"></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="#9BA6B6"></path>
|
||||
</g>
|
||||
</svg>
|
||||
Join our Slack
|
||||
</a>
|
||||
)}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<a
|
||||
href="mailto:feedback@calendso.com"
|
||||
className={classNames(
|
||||
active ? "bg-gray-100 text-gray-900" : "text-neutral-700",
|
||||
"flex px-4 py-2 text-sm font-medium"
|
||||
)}>
|
||||
<ChatAltIcon
|
||||
className={classNames(
|
||||
"text-neutral-400 group-hover:text-neutral-500",
|
||||
"mr-2 flex-shrink-0 h-5 w-5"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Feedback
|
||||
</a>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</div>
|
||||
<div className="py-1">
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<Link href="/auth/logout">
|
||||
<a
|
||||
className={classNames(
|
||||
active ? "bg-gray-100 text-gray-900" : "text-gray-700",
|
||||
"flex px-4 py-2 text-sm font-medium"
|
||||
)}>
|
||||
<LogoutIcon
|
||||
className={classNames(
|
||||
"text-neutral-400 group-hover:text-neutral-500",
|
||||
"mr-2 flex-shrink-0 h-5 w-5"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Sign out
|
||||
</a>
|
||||
</Link>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ const TimeOptions = (props) => {
|
|||
onChange={setIs24hClock}
|
||||
className={classNames(
|
||||
is24hClock ? "bg-blue-600" : "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-blue-500"
|
||||
"relative inline-flex flex-shrink-0 h-5 w-8 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black"
|
||||
)}>
|
||||
<span className="sr-only">Use setting</span>
|
||||
<span
|
||||
|
@ -62,7 +62,7 @@ const TimeOptions = (props) => {
|
|||
id="timeZone"
|
||||
value={selectedTimeZone}
|
||||
onChange={(tz) => setSelectedTimeZone(tz.value)}
|
||||
className="mb-2 shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
className="mb-2 shadow-sm focus:ring-black focus:border-black mt-1 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -40,8 +40,8 @@ export default function EditTeamModal(props) {
|
|||
|
||||
<div className="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left 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 mb-4">
|
||||
<div className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<UsersIcon className="h-6 w-6 text-blue-600" />
|
||||
<div className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-black bg-opacity-10 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<UsersIcon className="h-6 w-6 text-black" />
|
||||
</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">Edit the {props.team.name} team</h3>
|
||||
|
@ -82,7 +82,7 @@ export default function EditTeamModal(props) {
|
|||
<div className="mb-4 border border-red-400 rounded p-2 px-4">
|
||||
<p className="block text-sm font-medium text-gray-700">Tick the box to disband this team.</p>
|
||||
<label className="mt-1">
|
||||
<input type="checkbox" onChange={(e) => setCheckedDisbandTeam(e.target.checked)} className="shadow-sm mr-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm border-gray-300 rounded-md" />
|
||||
<input type="checkbox" onChange={(e) => setCheckedDisbandTeam(e.target.checked)} className="shadow-sm mr-2 focus:ring-black focus:border-black sm:text-sm border-gray-300 rounded-md" />
|
||||
Disband this team
|
||||
</label>
|
||||
</div>
|
||||
|
|
|
@ -38,60 +38,93 @@ export default function MemberInvitationModal(props) {
|
|||
});
|
||||
};
|
||||
|
||||
return (<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>
|
||||
return (
|
||||
<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>
|
||||
<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-lg px-4 pt-5 pb-4 text-left 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 mb-4">
|
||||
<div className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<UsersIcon className="h-6 w-6 text-blue-600" />
|
||||
<div className="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left 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 mb-4">
|
||||
<div className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-black bg-opacity-5 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<UsersIcon className="h-6 w-6 text-black" />
|
||||
</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">
|
||||
Invite a new member
|
||||
</h3>
|
||||
<div>
|
||||
<p className="text-sm text-gray-400">Invite someone to your team.</p>
|
||||
</div>
|
||||
</div>
|
||||
</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">Invite a new member</h3>
|
||||
<form onSubmit={inviteMember}>
|
||||
<div>
|
||||
<p className="text-sm text-gray-400">
|
||||
Invite someone to your team.
|
||||
<div className="mb-4">
|
||||
<label htmlFor="inviteUser" className="block text-sm font-medium text-gray-700">
|
||||
Email or Username
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="inviteUser"
|
||||
id="inviteUser"
|
||||
placeholder="email@example.com"
|
||||
required
|
||||
className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-black focus:border-black sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className="block tracking-wide text-gray-700 text-sm font-medium mb-2" htmlFor="role">
|
||||
Role
|
||||
</label>
|
||||
<select
|
||||
id="role"
|
||||
className="shadow-sm focus:ring-black focus:border-black mt-1 block w-full sm:text-sm border-gray-300 rounded-md">
|
||||
<option value="MEMBER">Member</option>
|
||||
<option value="OWNER">Owner</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="relative flex items-start">
|
||||
<div className="flex items-center h-5">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="sendInviteEmail"
|
||||
defaultChecked
|
||||
id="sendInviteEmail"
|
||||
className="text-black shadow-sm focus:ring-black focus:border-black sm:text-sm border-gray-300 rounded-md"
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-2 text-sm">
|
||||
<label htmlFor="sendInviteEmail" className="font-medium text-gray-700">
|
||||
Send an invite email
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{errorMessage && (
|
||||
<p className="text-red-700 text-sm">
|
||||
<span className="font-bold">Error: </span>
|
||||
{errorMessage}
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<button type="submit" className="btn btn-primary">
|
||||
Invite
|
||||
</button>
|
||||
<button onClick={props.onExit} type="button" className="btn btn-white mr-2">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<form onSubmit={inviteMember}>
|
||||
<div>
|
||||
<div className="mb-4">
|
||||
<label htmlFor="inviteUser" className="block text-sm font-medium text-gray-700">Email or Username</label>
|
||||
<input type="text" name="inviteUser" id="inviteUser" placeholder="email@example.com" required className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" />
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className="block tracking-wide text-gray-700 text-sm font-medium mb-2"
|
||||
htmlFor="role">
|
||||
Role
|
||||
</label>
|
||||
<select id="role" className="shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md">
|
||||
<option value="MEMBER">Member</option>
|
||||
<option value="OWNER">Owner</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className="mt-1 text-gray-600">
|
||||
<input type="checkbox" name="sendInviteEmail" defaultChecked id="sendInviteEmail" className="shadow-sm mr-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm border-gray-300 rounded-md" />
|
||||
Send an invite email
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{errorMessage && <p className="text-red-700 text-sm"><span className="font-bold">Error: </span>{errorMessage}</p>}
|
||||
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<button type="submit" className="btn btn-primary">
|
||||
Invite
|
||||
</button>
|
||||
<button onClick={props.onExit} type="button" className="btn btn-white mr-2">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>);
|
||||
);
|
||||
}
|
|
@ -22,7 +22,7 @@ export default function TeamList(props) {
|
|||
};
|
||||
|
||||
return (<div>
|
||||
<ul className="border px-2 mb-2 rounded divide-y divide-gray-200">
|
||||
<ul className="bg-white border px-2 mb-2 rounded divide-y divide-gray-200">
|
||||
{props.teams.map(
|
||||
(team: any) => <TeamListItem onChange={props.onChange} key={team.id} team={team} onActionSelect={
|
||||
(action: string) => selectAction(action, team)
|
||||
|
|
|
@ -26,30 +26,30 @@ export default function TeamListItem(props) {
|
|||
<div>
|
||||
<UsersIcon className="text-gray-400 group-hover:text-gray-500 flex-shrink-0 -mt-4 mr-2 h-6 w-6 inline"/>
|
||||
<div className="inline-block -mt-1">
|
||||
<span className="font-bold text-blue-700 text-sm">{props.team.name}</span>
|
||||
<span className="font-bold text-neutral-700 text-sm">{props.team.name}</span>
|
||||
<span className="text-xs text-gray-400 -mt-1 block capitalize">{props.team.role.toLowerCase()}</span>
|
||||
</div>
|
||||
</div>
|
||||
{props.team.role === 'INVITEE' && <div>
|
||||
<button className="btn-sm bg-transparent text-green-500 border border-green-500 px-3 py-1 rounded ml-2" onClick={acceptInvite}>Accept invitation</button>
|
||||
<button className="btn-sm bg-transparent text-green-500 border border-green-500 px-3 py-1 rounded-sm ml-2" onClick={acceptInvite}>Accept invitation</button>
|
||||
<button className="btn-sm bg-transparent px-2 py-1 ml-1">
|
||||
<TrashIcon className="h-6 w-6 inline text-gray-400 -mt-1" onClick={declineInvite} />
|
||||
</button>
|
||||
</div>}
|
||||
{props.team.role === 'MEMBER' && <div>
|
||||
<button onClick={declineInvite} className="btn-sm bg-transparent text-gray-400 border border-gray-400 px-3 py-1 rounded ml-2">Leave</button>
|
||||
<button onClick={declineInvite} className="btn-sm bg-transparent text-gray-400 border border-gray-400 px-3 py-1 rounded-sm ml-2">Leave</button>
|
||||
</div>}
|
||||
{props.team.role === 'OWNER' && <div>
|
||||
<Dropdown className="relative inline-block text-left">
|
||||
<button className="btn-sm bg-transparent text-gray-400 px-3 py-1 rounded ml-2">
|
||||
<button className="btn-sm bg-transparent text-gray-400 px-3 py-1 rounded-sm ml-2">
|
||||
<CogIcon className="h-6 w-6 inline text-gray-400" />
|
||||
</button>
|
||||
<ul role="menu" className="z-10 origin-top-right absolute right-0 w-36 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none">
|
||||
<ul role="menu" className="z-10 origin-top-right absolute right-0 w-36 rounded-sm shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none">
|
||||
<li className="text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900" role="menuitem">
|
||||
<a className="block px-4 py-2" onClick={() => props.onActionSelect('invite')}>Invite member(s)</a>
|
||||
<button className="block px-4 py-2" onClick={() => props.onActionSelect('invite')}>Invite members</button>
|
||||
</li>
|
||||
<li className="text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900" role="menuitem">
|
||||
<a className="block px-4 py-2" onClick={() => props.onActionSelect('edit')}>Manage team</a>
|
||||
<button className="block px-4 py-2" onClick={() => props.onActionSelect('edit')}>Manage team</button>
|
||||
</li>
|
||||
</ul>
|
||||
</Dropdown>
|
||||
|
@ -64,14 +64,14 @@ export default function TeamListItem(props) {
|
|||
<td className="py-1 pl-2">Alex van Andel ({ member.email })</td>
|
||||
<td>Owner</td>
|
||||
<td className="text-right p-1">
|
||||
<button className="btn-sm text-xs bg-transparent text-red-400 border border-red-400 px-3 py-1 rounded ml-2"><UserRemoveIcon className="text-red-400 group-hover:text-gray-500 flex-shrink-0 -mt-1 mr-1 h-4 w-4 inline"/>Remove</button>
|
||||
<button className="btn-sm text-xs bg-transparent text-red-400 border border-red-400 px-3 py-1 rounded-sm ml-2"><UserRemoveIcon className="text-red-400 group-hover:text-gray-500 flex-shrink-0 -mt-1 mr-1 h-4 w-4 inline"/>Remove</button>
|
||||
</td>
|
||||
</tr>)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>}
|
||||
<button className="btn-sm bg-transparent text-gray-400 border border-gray-400 px-3 py-1 rounded"><UserAddIcon className="text-gray-400 group-hover:text-gray-500 flex-shrink-0 -mt-1 h-6 w-6 inline"/> Invite member</button>
|
||||
<button className="btn-sm bg-transparent text-red-400 border border-red-400 px-3 py-1 rounded ml-2">Disband</button>
|
||||
<button className="btn-sm bg-transparent text-gray-400 border border-gray-400 px-3 py-1 rounded-sm"><UserAddIcon className="text-gray-400 group-hover:text-gray-500 flex-shrink-0 -mt-1 h-6 w-6 inline"/> Invite member</button>
|
||||
<button className="btn-sm bg-transparent text-red-400 border border-red-400 px-3 py-1 rounded-sm ml-2">Disband</button>
|
||||
</div>}*/}
|
||||
</li>);
|
||||
}
|
|
@ -69,10 +69,13 @@ export const Scheduler = ({
|
|||
};
|
||||
|
||||
const OpeningHours = ({ idx, item }) => (
|
||||
<li className="py-2 flex justify-between border-t">
|
||||
<div className="flex flex-col space-y-4 lg:inline-flex ml-2 ">
|
||||
<li className="py-2 flex justify-between border-b">
|
||||
<div className="flex flex-col space-y-4 lg:inline-flex">
|
||||
<WeekdaySelect defaultValue={item.days} onSelect={(selected: number[]) => (item.days = selected)} />
|
||||
<button className="ml-2 text-sm px-2" type="button" onClick={() => setEditSchedule(idx)}>
|
||||
<button
|
||||
className="text-sm bg-neutral-100 rounded-sm py-2 px-3"
|
||||
type="button"
|
||||
onClick={() => setEditSchedule(idx)}>
|
||||
{dayjs()
|
||||
.startOf("day")
|
||||
.add(item.startTime, "minutes")
|
||||
|
@ -95,9 +98,9 @@ export const Scheduler = ({
|
|||
|
||||
return (
|
||||
<div>
|
||||
<div className="rounded border flex">
|
||||
<div className="flex">
|
||||
<div className="w-full">
|
||||
<div className=" p-2">
|
||||
<div className="">
|
||||
<label htmlFor="timeZone" className="block text-sm font-medium text-gray-700">
|
||||
Timezone
|
||||
</label>
|
||||
|
@ -106,7 +109,7 @@ export const Scheduler = ({
|
|||
id="timeZone"
|
||||
value={selectedTimeZone}
|
||||
onChange={(tz) => setTimeZone(tz.value)}
|
||||
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
className="shadow-sm focus:ring-black focus:border-black mt-1 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -115,8 +118,7 @@ export const Scheduler = ({
|
|||
<OpeningHours key={idx} idx={idx} item={item} />
|
||||
))}
|
||||
</ul>
|
||||
<hr />
|
||||
<button type="button" onClick={addNewSchedule} className="btn-white btn-sm m-2">
|
||||
<button type="button" onClick={addNewSchedule} className="btn-white btn-sm mt-2">
|
||||
Add another
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -7,7 +7,7 @@ const UsernameInput = React.forwardRef((props, ref) => (
|
|||
Username
|
||||
</label>
|
||||
<div className="mt-1 rounded-md shadow-sm flex">
|
||||
<span className="bg-gray-50 border border-r-0 border-gray-300 rounded-l-md px-3 inline-flex items-center text-gray-500 sm:text-sm">
|
||||
<span className="bg-gray-50 border border-r-0 border-gray-300 rounded-l-sm px-3 inline-flex items-center text-gray-500 sm:text-sm">
|
||||
{typeof window !== "undefined" && window.location.hostname}/
|
||||
</span>
|
||||
<input
|
||||
|
@ -18,7 +18,7 @@ const UsernameInput = React.forwardRef((props, ref) => (
|
|||
autoComplete="username"
|
||||
required
|
||||
{...props}
|
||||
className="focus:ring-blue-500 focus:border-blue-500 flex-grow block w-full min-w-0 rounded-none rounded-r-md sm:text-sm border-gray-300 lowercase"
|
||||
className="focus:ring-black focus:border-black flex-grow block w-full min-w-0 rounded-none rounded-r-sm sm:text-sm border-gray-300 lowercase"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -25,9 +25,9 @@ export const WeekdaySelect = (props) => {
|
|||
<button
|
||||
key={idx}
|
||||
onClick={(e) => toggleDay(e, idx)}
|
||||
style={{ marginLeft: "-2px" }}
|
||||
className={`
|
||||
active focus:outline-none border-2 border-blue-500 px-2 py-1 rounded
|
||||
w-10 h-10
|
||||
bg-black text-white focus:outline-none px-3 py-1 rounded
|
||||
${activeDays[idx + 1] ? "rounded-r-none" : ""}
|
||||
${activeDays[idx - 1] ? "rounded-l-none" : ""}
|
||||
${idx === 0 ? "rounded-l" : ""}
|
||||
|
@ -40,7 +40,7 @@ export const WeekdaySelect = (props) => {
|
|||
key={idx}
|
||||
onClick={(e) => toggleDay(e, idx)}
|
||||
style={{ marginTop: "1px", marginBottom: "1px" }}
|
||||
className={`border focus:outline-none px-2 py-1 rounded-none ${
|
||||
className={`w-10 h-10 bg-gray-50 focus:outline-none px-3 py-1 rounded-none ${
|
||||
idx === 0 ? "rounded-l" : "border-l-0"
|
||||
} ${idx === days.length - 1 ? "rounded-r" : ""}`}>
|
||||
{day}
|
||||
|
|
|
@ -42,7 +42,7 @@ export default function SetTimesModal(props) {
|
|||
<div className="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6">
|
||||
<div className="sm:flex sm:items-start mb-4">
|
||||
<div className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<ClockIcon className="h-6 w-6 text-blue-600" />
|
||||
<ClockIcon className="h-6 w-6 text-black" />
|
||||
</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">
|
||||
|
@ -67,7 +67,7 @@ export default function SetTimesModal(props) {
|
|||
maxLength="2"
|
||||
name="hours"
|
||||
id="startHours"
|
||||
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
className="shadow-sm focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
placeholder="9"
|
||||
defaultValue={startHours}
|
||||
/>
|
||||
|
@ -86,7 +86,7 @@ export default function SetTimesModal(props) {
|
|||
maxLength="2"
|
||||
name="minutes"
|
||||
id="startMinutes"
|
||||
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
className="shadow-sm focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
placeholder="30"
|
||||
defaultValue={startMinutes}
|
||||
/>
|
||||
|
@ -106,7 +106,7 @@ export default function SetTimesModal(props) {
|
|||
maxLength="2"
|
||||
name="hours"
|
||||
id="endHours"
|
||||
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
className="shadow-sm focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
placeholder="17"
|
||||
defaultValue={endHours}
|
||||
/>
|
||||
|
@ -125,7 +125,7 @@ export default function SetTimesModal(props) {
|
|||
step="15"
|
||||
name="minutes"
|
||||
id="endMinutes"
|
||||
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
className="shadow-sm focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
placeholder="30"
|
||||
defaultValue={endMinutes}
|
||||
/>
|
||||
|
|
|
@ -201,7 +201,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-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
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"
|
||||
placeholder="John Doe"
|
||||
defaultValue={props.booking ? props.booking.attendees[0].name : ""}
|
||||
/>
|
||||
|
@ -219,7 +219,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-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
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"
|
||||
placeholder="you@example.com"
|
||||
defaultValue={props.booking ? props.booking.attendees[0].email : ""}
|
||||
/>
|
||||
|
@ -257,7 +257,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-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
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"
|
||||
onChange={() => {
|
||||
/* DO NOT REMOVE: Callback required by PhoneInput, comment added to satisfy eslint:no-empty-function */
|
||||
}}
|
||||
|
@ -283,7 +283,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-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
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"
|
||||
placeholder=""
|
||||
/>
|
||||
)}
|
||||
|
@ -293,7 +293,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-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
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"
|
||||
placeholder=""
|
||||
/>
|
||||
)}
|
||||
|
@ -303,7 +303,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-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
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"
|
||||
placeholder=""
|
||||
/>
|
||||
)}
|
||||
|
@ -313,7 +313,7 @@ export default function Book(props: any): JSX.Element {
|
|||
type="checkbox"
|
||||
name={"custom_" + input.id}
|
||||
id={"custom_" + input.id}
|
||||
className="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded mr-2"
|
||||
className="focus:ring-black h-4 w-4 text-blue-600 border-gray-300 rounded mr-2"
|
||||
placeholder=""
|
||||
/>
|
||||
<label
|
||||
|
@ -371,7 +371,7 @@ 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-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
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"
|
||||
placeholder="Please share anything that will help prepare for our meeting."
|
||||
defaultValue={props.booking ? props.booking.description : ""}
|
||||
/>
|
||||
|
|
|
@ -33,7 +33,7 @@ export default function Error() {
|
|||
</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-blue-500 sm:text-sm">
|
||||
<a className="inline-flex justify-center w-full rounded-sm border border-transparent shadow-sm px-4 py-2 bg-neutral-900 text-base font-medium text-white hover:bg-neutral-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-500 sm:text-sm">
|
||||
Go back to the login page
|
||||
</a>
|
||||
</Link>
|
||||
|
|
|
@ -83,7 +83,7 @@ export default function Page({ resetPasswordRequest, csrfToken }: Props) {
|
|||
<Link href="/auth/login">
|
||||
<button
|
||||
type="button"
|
||||
className="w-full flex justify-center py-2 px-4 text-sm font-medium text-blue-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
className="w-full flex justify-center py-2 px-4 text-sm font-medium text-blue-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black">
|
||||
Login
|
||||
</button>
|
||||
</Link>
|
||||
|
@ -107,7 +107,7 @@ export default function Page({ resetPasswordRequest, csrfToken }: Props) {
|
|||
<Link href="/auth/forgot-password">
|
||||
<button
|
||||
type="button"
|
||||
className="w-full flex justify-center py-2 px-4 text-sm font-medium text-blue-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
className="w-full flex justify-center py-2 px-4 text-sm font-medium text-blue-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black">
|
||||
Try Again
|
||||
</button>
|
||||
</Link>
|
||||
|
@ -151,7 +151,7 @@ export default function Page({ resetPasswordRequest, csrfToken }: Props) {
|
|||
type="password"
|
||||
autoComplete="password"
|
||||
required
|
||||
className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||||
className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-black focus:border-black sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -160,7 +160,7 @@ export default function Page({ resetPasswordRequest, csrfToken }: Props) {
|
|||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className={`w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 ${
|
||||
className={`w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black ${
|
||||
loading ? "cursor-not-allowed" : ""
|
||||
}`}>
|
||||
{loading && (
|
||||
|
|
|
@ -104,7 +104,7 @@ export default function Page({ csrfToken }) {
|
|||
autoComplete="email"
|
||||
placeholder="john.doe@example.com"
|
||||
required
|
||||
className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||||
className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-black focus:border-black sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -113,7 +113,7 @@ export default function Page({ csrfToken }) {
|
|||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className={`w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 ${
|
||||
className={`w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-black hover:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black ${
|
||||
loading ? "cursor-not-allowed" : ""
|
||||
}`}>
|
||||
{loading && (
|
||||
|
@ -142,7 +142,7 @@ export default function Page({ csrfToken }) {
|
|||
<Link href="/auth/login">
|
||||
<button
|
||||
type="button"
|
||||
className="w-full flex justify-center py-2 px-4 text-sm font-medium text-blue-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
className="w-full flex justify-center py-2 px-4 text-sm font-medium text-black focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black">
|
||||
Login
|
||||
</button>
|
||||
</Link>
|
||||
|
|
|
@ -4,21 +4,22 @@ import { getCsrfToken } from "next-auth/client";
|
|||
|
||||
export default function Login({ csrfToken }) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="min-h-screen bg-neutral-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<Head>
|
||||
<title>Login</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">Sign in to your account</h2>
|
||||
<img className="h-6 mx-auto" src="/calendso-logo-white-word.svg" alt="Calendso Logo" />
|
||||
<h2 className="mt-6 text-center text-3xl font-bold text-neutral-900">Sign in to your account</h2>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="bg-white py-8 px-4 mx-2 shadow rounded-lg sm:px-10">
|
||||
<div className="bg-white py-8 px-4 mx-2 rounded-sm sm:px-10 border border-neutral-200">
|
||||
<form className="space-y-6" method="post" action="/api/auth/callback/credentials">
|
||||
<input name="csrfToken" type="hidden" defaultValue={csrfToken} hidden />
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
|
||||
<label htmlFor="email" className="block text-sm font-medium text-neutral-700">
|
||||
Email address
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
|
@ -27,26 +28,33 @@ export default function Login({ csrfToken }) {
|
|||
name="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
placeholder="john.doe@example.com"
|
||||
required
|
||||
className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||||
className="appearance-none block w-full px-3 py-2 border border-neutral-300 rounded-sm shadow-sm placeholder-gray-400 focus:outline-none focus:ring-neutral-900 focus:border-neutral-900 sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
|
||||
Password
|
||||
</label>
|
||||
<div className="flex">
|
||||
<div className="w-1/2">
|
||||
<label htmlFor="password" className="block text-sm font-medium text-neutral-700">
|
||||
Password
|
||||
</label>
|
||||
</div>
|
||||
<div className="w-1/2 text-right">
|
||||
<Link href="/auth/forgot-password">
|
||||
<a className="font-medium text-secondary-600 text-sm">Forgot?</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-1">
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
placeholder="•••••••••••••"
|
||||
required
|
||||
className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||||
className="appearance-none block w-full px-3 py-2 border border-neutral-300 rounded-sm shadow-sm placeholder-gray-400 focus:outline-none focus:ring-neutral-900 focus:border-neutral-900 sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -54,19 +62,18 @@ export default function Login({ csrfToken }) {
|
|||
<div className="space-y-2">
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
className="w-full 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-black">
|
||||
Sign in
|
||||
</button>
|
||||
<Link href="/auth/forgot-password">
|
||||
<button
|
||||
type="button"
|
||||
className="w-full flex justify-center py-2 px-4 text-sm font-medium text-blue-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
Forgot Password?
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div className="mt-4 text-neutral-600 text-center text-sm">
|
||||
Don't have an account? {/* replace this with your account creation flow */}
|
||||
<a href="https://checkout.calendso.com" className="font-medium text-neutral-900">
|
||||
Create an account
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -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-blue-500 sm:text-sm">
|
||||
<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">
|
||||
Go back to the login page
|
||||
</a>
|
||||
</Link>
|
||||
|
|
|
@ -75,20 +75,20 @@ export default function Signup(props) {
|
|||
</div>
|
||||
<div className="mb-2">
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700">Email</label>
|
||||
<input type="email" name="email" id="email" placeholder="jdoe@example.com" disabled={!!props.email} readOnly={!!props.email} value={props.email} className="bg-gray-100 mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" />
|
||||
<input type="email" name="email" id="email" placeholder="jdoe@example.com" disabled={!!props.email} readOnly={!!props.email} value={props.email} className="bg-gray-100 mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-black focus:border-black sm:text-sm" />
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<label htmlFor="password" className="block text-sm font-medium text-gray-700">Password</label>
|
||||
<input type="password" name="password" id="password" required placeholder="•••••••••••••" className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" />
|
||||
<input type="password" name="password" id="password" required placeholder="•••••••••••••" className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-black focus:border-black sm:text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="passwordcheck" className="block text-sm font-medium text-gray-700">Confirm password</label>
|
||||
<input type="password" name="passwordcheck" id="passwordcheck" required placeholder="•••••••••••••" className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" />
|
||||
<input type="password" name="passwordcheck" id="passwordcheck" required placeholder="•••••••••••••" className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-black focus:border-black sm:text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 sm:mt-4 flex">
|
||||
<input type="submit" value="Create Account"
|
||||
className="btn btn-primary w-7/12 mr-2 inline-flex justify-center rounded-md border border-transparent cursor-pointer 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-blue-500 sm:text-sm" />
|
||||
className="btn btn-primary w-7/12 mr-2 inline-flex justify-center rounded-md border border-transparent cursor-pointer 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 onClick={() => signIn('Calendso', { callbackUrl: (router.query.callbackUrl || '') as string })}
|
||||
className="w-5/12 inline-flex justify-center text-sm text-gray-500 font-medium border px-4 py-2 rounded btn cursor-pointer">Login instead</a>
|
||||
</div>
|
||||
|
|
|
@ -316,7 +316,7 @@ export default function EventTypePage({
|
|||
name="address"
|
||||
id="address"
|
||||
required
|
||||
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
className="shadow-sm focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
defaultValue={locations.find((location) => location.type === LocationType.InPerson)?.address}
|
||||
/>
|
||||
</div>
|
||||
|
@ -390,7 +390,7 @@ export default function EventTypePage({
|
|||
name="title"
|
||||
id="title"
|
||||
required
|
||||
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
className="shadow-sm focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
placeholder="Quick Chat"
|
||||
defaultValue={eventType.title}
|
||||
/>
|
||||
|
@ -411,7 +411,7 @@ export default function EventTypePage({
|
|||
name="slug"
|
||||
id="slug"
|
||||
required
|
||||
className="flex-1 block w-full focus:ring-blue-500 focus:border-blue-500 min-w-0 rounded-none rounded-r-md sm:text-sm border-gray-300"
|
||||
className="flex-1 block w-full focus:ring-black focus:border-black min-w-0 rounded-none rounded-r-md sm:text-sm border-gray-300"
|
||||
defaultValue={eventType.slug}
|
||||
/>
|
||||
</div>
|
||||
|
@ -429,7 +429,8 @@ export default function EventTypePage({
|
|||
id="location"
|
||||
options={locationOptions}
|
||||
isSearchable="false"
|
||||
className="flex-1 block w-full focus:ring-blue-500 focus:border-blue-500 min-w-0 rounded-none rounded-r-md sm:text-sm border-gray-300"
|
||||
classNamePrefix="react-select"
|
||||
className="react-select-container rounded-sm border border-gray-300 flex-1 block w-full focus:ring-primary-500 focus:border-primary-500 min-w-0 sm:text-sm"
|
||||
onChange={(e) => openLocationModal(e.value)}
|
||||
/>
|
||||
</div>
|
||||
|
@ -551,7 +552,7 @@ export default function EventTypePage({
|
|||
ref={descriptionRef}
|
||||
name="description"
|
||||
id="description"
|
||||
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
className="shadow-sm focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
placeholder="A quick video meeting."
|
||||
defaultValue={eventType.description}></textarea>
|
||||
</div>
|
||||
|
@ -566,7 +567,7 @@ export default function EventTypePage({
|
|||
type="text"
|
||||
name="title"
|
||||
id="title"
|
||||
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
className="shadow-sm focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
placeholder="Meeting with {USER}"
|
||||
defaultValue={eventType.eventName}
|
||||
/>
|
||||
|
@ -626,7 +627,7 @@ export default function EventTypePage({
|
|||
id="ishidden"
|
||||
name="ishidden"
|
||||
type="checkbox"
|
||||
className="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded"
|
||||
className="focus:ring-black h-4 w-4 text-blue-600 border-gray-300 rounded"
|
||||
defaultChecked={eventType.hidden}
|
||||
/>
|
||||
</div>
|
||||
|
@ -648,7 +649,7 @@ export default function EventTypePage({
|
|||
id="requiresConfirmation"
|
||||
name="requiresConfirmation"
|
||||
type="checkbox"
|
||||
className="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded"
|
||||
className="focus:ring-black h-4 w-4 text-blue-600 border-gray-300 rounded"
|
||||
defaultChecked={eventType.requiresConfirmation}
|
||||
/>
|
||||
</div>
|
||||
|
@ -677,7 +678,7 @@ export default function EventTypePage({
|
|||
name="minimumAdvance"
|
||||
id="minimumAdvance"
|
||||
required
|
||||
className="focus:ring-blue-500 focus:border-blue-500 block w-full pr-20 sm:text-sm border-gray-300 rounded-md"
|
||||
className="focus:ring-black focus:border-black block w-full pr-20 sm:text-sm border-gray-300 rounded-md"
|
||||
defaultValue={eventType.minimumBookingNotice}
|
||||
/>
|
||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 text-sm">
|
||||
|
@ -734,7 +735,7 @@ export default function EventTypePage({
|
|||
type="text"
|
||||
name="periodDays"
|
||||
id=""
|
||||
className="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-12 sm:text-sm border-gray-300 rounded-md"
|
||||
className="shadow-sm focus:ring-black focus:border-indigo-500 block w-12 sm:text-sm border-gray-300 rounded-md"
|
||||
placeholder="30"
|
||||
defaultValue={eventType.periodDays || 30}
|
||||
/>
|
||||
|
@ -742,7 +743,7 @@ export default function EventTypePage({
|
|||
ref={periodDaysTypeRef}
|
||||
id=""
|
||||
name="periodDaysType"
|
||||
className=" block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"
|
||||
className=" block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-black focus:border-indigo-500 sm:text-sm rounded-md"
|
||||
defaultValue={eventType.periodCountCalendarDays ? "1" : "0"}>
|
||||
<option value="1">calendar days</option>
|
||||
<option value="0">business days</option>
|
||||
|
@ -793,7 +794,7 @@ export default function EventTypePage({
|
|||
name="length"
|
||||
id="length"
|
||||
required
|
||||
className="focus:ring-blue-500 focus:border-blue-500 block w-full pr-20 sm:text-sm border-gray-300 rounded-md"
|
||||
className="focus:ring-black focus:border-black block w-full pr-20 sm:text-sm border-gray-300 rounded-md"
|
||||
placeholder="15"
|
||||
defaultValue={eventType.length}
|
||||
/>
|
||||
|
@ -863,7 +864,7 @@ export default function EventTypePage({
|
|||
<div className="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left 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 mb-4">
|
||||
<div className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<LocationMarkerIcon className="h-6 w-6 text-blue-600" />
|
||||
<LocationMarkerIcon className="h-6 w-6 text-black" />
|
||||
</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">
|
||||
|
@ -877,7 +878,7 @@ export default function EventTypePage({
|
|||
defaultValue={selectedLocation}
|
||||
options={locationOptions}
|
||||
isSearchable="false"
|
||||
className="mb-2 flex-1 block w-full focus:ring-blue-500 focus:border-blue-500 min-w-0 rounded-none rounded-r-md sm:text-sm border-gray-300"
|
||||
className="mb-2 flex-1 block w-full focus:ring-black focus:border-black min-w-0 rounded-none rounded-r-md sm:text-sm border-gray-300"
|
||||
onChange={setSelectedLocation}
|
||||
/>
|
||||
<LocationOptions />
|
||||
|
@ -913,7 +914,7 @@ export default function EventTypePage({
|
|||
<div className="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left 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 mb-4">
|
||||
<div className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<PlusIcon className="h-6 w-6 text-blue-600" />
|
||||
<PlusIcon className="h-6 w-6 text-black" />
|
||||
</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">
|
||||
|
@ -937,7 +938,7 @@ export default function EventTypePage({
|
|||
options={inputOptions}
|
||||
isSearchable="false"
|
||||
required
|
||||
className="mb-2 flex-1 block w-full focus:ring-blue-500 focus:border-blue-500 min-w-0 rounded-none rounded-r-md sm:text-sm border-gray-300 mt-1"
|
||||
className="react-select-container border border-gray-300 rounded-sm mb-2 flex-1 block w-full focus:ring-black focus:border-black min-w-0 sm:text-sm mt-1"
|
||||
onChange={setSelectedInputOption}
|
||||
/>
|
||||
</div>
|
||||
|
@ -951,7 +952,7 @@ export default function EventTypePage({
|
|||
name="label"
|
||||
id="label"
|
||||
required
|
||||
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
className="shadow-sm focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
defaultValue={selectedCustomInput?.label}
|
||||
/>
|
||||
</div>
|
||||
|
@ -961,7 +962,7 @@ export default function EventTypePage({
|
|||
id="required"
|
||||
name="required"
|
||||
type="checkbox"
|
||||
className="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded mr-2"
|
||||
className="focus:ring-black h-4 w-4 text-blue-600 border-gray-300 rounded mr-2"
|
||||
defaultChecked={selectedCustomInput?.required ?? true}
|
||||
/>
|
||||
<label htmlFor="required" className="block text-sm font-medium text-gray-700">
|
||||
|
@ -1060,7 +1061,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async ({ req, query
|
|||
enabled: credentials.find((integration) => integration.type === "google_calendar") != null,
|
||||
type: "google_calendar",
|
||||
title: "Google Calendar",
|
||||
imageSrc: "integrations/google-calendar.png",
|
||||
imageSrc: "integrations/google-calendar.svg",
|
||||
description: "For personal and business accounts",
|
||||
},
|
||||
{
|
||||
|
@ -1068,7 +1069,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async ({ req, query
|
|||
type: "office365_calendar",
|
||||
enabled: credentials.find((integration) => integration.type === "office365_calendar") != null,
|
||||
title: "Office 365 / Outlook.com Calendar",
|
||||
imageSrc: "integrations/office-365.png",
|
||||
imageSrc: "integrations/outlook.svg",
|
||||
description: "For personal and business accounts",
|
||||
},
|
||||
];
|
||||
|
|
|
@ -1,430 +1,349 @@
|
|||
import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
import prisma from '../../lib/prisma';
|
||||
import Modal from '../../components/Modal';
|
||||
import Shell from '../../components/Shell';
|
||||
import {useRouter} from 'next/router';
|
||||
import {useRef, useState} from 'react';
|
||||
import {getSession, useSession} from 'next-auth/client';
|
||||
import {ClockIcon, PlusIcon} from '@heroicons/react/outline';
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import prisma from "../../lib/prisma";
|
||||
import Modal from "../../components/Modal";
|
||||
import Shell from "../../components/Shell";
|
||||
import { useRouter } from "next/router";
|
||||
import { useRef, useState } from "react";
|
||||
import { getSession, useSession } from "next-auth/client";
|
||||
import { ClockIcon } from "@heroicons/react/outline";
|
||||
import Loader from '@components/Loader';
|
||||
|
||||
export default function Availability(props) {
|
||||
const [ session, loading ] = useSession();
|
||||
const router = useRouter();
|
||||
const [showAddModal, setShowAddModal] = useState(false);
|
||||
const [successModalOpen, setSuccessModalOpen] = useState(false);
|
||||
const [showChangeTimesModal, setShowChangeTimesModal] = useState(false);
|
||||
const titleRef = useRef<HTMLInputElement>();
|
||||
const slugRef = useRef<HTMLInputElement>();
|
||||
const descriptionRef = useRef<HTMLTextAreaElement>();
|
||||
const lengthRef = useRef<HTMLInputElement>();
|
||||
const isHiddenRef = useRef<HTMLInputElement>();
|
||||
const [session, loading] = useSession();
|
||||
const router = useRouter();
|
||||
const [showAddModal, setShowAddModal] = useState(false);
|
||||
const [successModalOpen, setSuccessModalOpen] = useState(false);
|
||||
const [showChangeTimesModal, setShowChangeTimesModal] = useState(false);
|
||||
const titleRef = useRef<HTMLInputElement>();
|
||||
const slugRef = useRef<HTMLInputElement>();
|
||||
const descriptionRef = useRef<HTMLTextAreaElement>();
|
||||
const lengthRef = useRef<HTMLInputElement>();
|
||||
const isHiddenRef = useRef<HTMLInputElement>();
|
||||
|
||||
const startHoursRef = useRef<HTMLInputElement>();
|
||||
const startMinsRef = useRef<HTMLInputElement>();
|
||||
const endHoursRef = useRef<HTMLInputElement>();
|
||||
const endMinsRef = useRef<HTMLInputElement>();
|
||||
const bufferHoursRef = useRef<HTMLInputElement>();
|
||||
const bufferMinsRef = useRef<HTMLInputElement>();
|
||||
const startHoursRef = useRef<HTMLInputElement>();
|
||||
const startMinsRef = useRef<HTMLInputElement>();
|
||||
const endHoursRef = useRef<HTMLInputElement>();
|
||||
const endMinsRef = useRef<HTMLInputElement>();
|
||||
const bufferHoursRef = useRef<HTMLInputElement>();
|
||||
const bufferMinsRef = useRef<HTMLInputElement>();
|
||||
|
||||
if (loading) {
|
||||
return <div className="loader"></div>;
|
||||
if (loading) {
|
||||
return <Loader/>;
|
||||
}
|
||||
|
||||
function toggleAddModal() {
|
||||
setShowAddModal(!showAddModal);
|
||||
}
|
||||
|
||||
function toggleChangeTimesModal() {
|
||||
setShowChangeTimesModal(!showChangeTimesModal);
|
||||
}
|
||||
|
||||
const closeSuccessModal = () => {
|
||||
setSuccessModalOpen(false);
|
||||
router.replace(router.asPath);
|
||||
};
|
||||
|
||||
function convertMinsToHrsMins(mins) {
|
||||
let h = Math.floor(mins / 60);
|
||||
let m = mins % 60;
|
||||
h = h < 10 ? "0" + h : h;
|
||||
m = m < 10 ? "0" + m : m;
|
||||
return `${h}:${m}`;
|
||||
}
|
||||
|
||||
async function createEventTypeHandler(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const enteredTitle = titleRef.current.value;
|
||||
const enteredSlug = slugRef.current.value;
|
||||
const enteredDescription = descriptionRef.current.value;
|
||||
const enteredLength = lengthRef.current.value;
|
||||
const enteredIsHidden = isHiddenRef.current.checked;
|
||||
|
||||
// TODO: Add validation
|
||||
|
||||
const response = await fetch("/api/availability/eventtype", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
title: enteredTitle,
|
||||
slug: enteredSlug,
|
||||
description: enteredDescription,
|
||||
length: enteredLength,
|
||||
hidden: enteredIsHidden,
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (enteredTitle && enteredLength) {
|
||||
router.replace(router.asPath);
|
||||
toggleAddModal();
|
||||
}
|
||||
}
|
||||
|
||||
function autoPopulateSlug() {
|
||||
let t = titleRef.current.value;
|
||||
t = t.replace(/\s+/g, '-').toLowerCase();
|
||||
slugRef.current.value = t;
|
||||
}
|
||||
async function updateStartEndTimesHandler(event) {
|
||||
event.preventDefault();
|
||||
|
||||
function toggleAddModal() {
|
||||
setShowAddModal(!showAddModal);
|
||||
}
|
||||
const enteredStartHours = parseInt(startHoursRef.current.value);
|
||||
const enteredStartMins = parseInt(startMinsRef.current.value);
|
||||
const enteredEndHours = parseInt(endHoursRef.current.value);
|
||||
const enteredEndMins = parseInt(endMinsRef.current.value);
|
||||
const enteredBufferHours = parseInt(bufferHoursRef.current.value);
|
||||
const enteredBufferMins = parseInt(bufferMinsRef.current.value);
|
||||
|
||||
function toggleChangeTimesModal() {
|
||||
setShowChangeTimesModal(!showChangeTimesModal);
|
||||
}
|
||||
const startMins = enteredStartHours * 60 + enteredStartMins;
|
||||
const endMins = enteredEndHours * 60 + enteredEndMins;
|
||||
const bufferMins = enteredBufferHours * 60 + enteredBufferMins;
|
||||
|
||||
const closeSuccessModal = () => { setSuccessModalOpen(false); router.replace(router.asPath); }
|
||||
// TODO: Add validation
|
||||
|
||||
function convertMinsToHrsMins (mins) {
|
||||
let h = Math.floor(mins / 60);
|
||||
let m = mins % 60;
|
||||
h = h < 10 ? '0' + h : h;
|
||||
m = m < 10 ? '0' + m : m;
|
||||
return `${h}:${m}`;
|
||||
}
|
||||
const response = await fetch("/api/availability/day", {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify({ start: startMins, end: endMins, buffer: bufferMins }),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
async function createEventTypeHandler(event) {
|
||||
event.preventDefault();
|
||||
setShowChangeTimesModal(false);
|
||||
setSuccessModalOpen(true);
|
||||
}
|
||||
|
||||
const enteredTitle = titleRef.current.value;
|
||||
const enteredSlug = slugRef.current.value;
|
||||
const enteredDescription = descriptionRef.current.value;
|
||||
const enteredLength = lengthRef.current.value;
|
||||
const enteredIsHidden = isHiddenRef.current.checked;
|
||||
return (
|
||||
<div>
|
||||
<Head>
|
||||
<title>Availability | Calendso</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<Shell
|
||||
heading="Availability"
|
||||
subtitle="Configure times when you are available for bookings.
|
||||
|
||||
// TODO: Add validation
|
||||
">
|
||||
<div className="flex">
|
||||
<div className="w-1/2 mr-2 bg-white shadow 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
|
||||
</h3>
|
||||
<div className="mt-2 max-w-xl text-sm text-gray-500">
|
||||
<p>
|
||||
Currently, your day is set to start at {convertMinsToHrsMins(props.user.startTime)} and end
|
||||
at {convertMinsToHrsMins(props.user.endTime)}.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<button onClick={toggleChangeTimesModal} type="button" className="btn btn-primary">
|
||||
Change available times
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
const response = await fetch('/api/availability/eventtype', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({title: enteredTitle, slug: enteredSlug, description: enteredDescription, length: enteredLength, hidden: enteredIsHidden}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (enteredTitle && enteredLength) {
|
||||
router.replace(router.asPath);
|
||||
toggleAddModal();
|
||||
}
|
||||
}
|
||||
|
||||
async function updateStartEndTimesHandler(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const enteredStartHours = parseInt(startHoursRef.current.value);
|
||||
const enteredStartMins = parseInt(startMinsRef.current.value);
|
||||
const enteredEndHours = parseInt(endHoursRef.current.value);
|
||||
const enteredEndMins = parseInt(endMinsRef.current.value);
|
||||
const enteredBufferHours = parseInt(bufferHoursRef.current.value);
|
||||
const enteredBufferMins = parseInt(bufferMinsRef.current.value);
|
||||
|
||||
const startMins = enteredStartHours * 60 + enteredStartMins;
|
||||
const endMins = enteredEndHours * 60 + enteredEndMins;
|
||||
const bufferMins = enteredBufferHours * 60 + enteredBufferMins;
|
||||
|
||||
// TODO: Add validation
|
||||
|
||||
const response = await fetch('/api/availability/day', {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({start: startMins, end: endMins, buffer: bufferMins}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
setShowChangeTimesModal(false);
|
||||
setSuccessModalOpen(true);
|
||||
}
|
||||
|
||||
return(
|
||||
<div>
|
||||
<Head>
|
||||
<title>Availability | Calendso</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<Shell heading="Availability">
|
||||
<div className="mb-4 sm:flex sm:items-center sm:justify-between">
|
||||
<h3 className="text-lg leading-6 font-medium text-white">
|
||||
Event Types
|
||||
</h3>
|
||||
<div className="mt-3 sm:mt-0 sm:ml-4">
|
||||
<button onClick={toggleAddModal} type="button" className="btn-sm btn-white">
|
||||
New event type
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col mb-8">
|
||||
<div className="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
||||
<div className="py-2 align-middle inline-block max-w-full min-w-full sm:px-6 lg:px-8">
|
||||
<div className="shadow overflow-hidden border-b border-gray-200 rounded-lg">
|
||||
<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">
|
||||
Name
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Description
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Length
|
||||
</th>
|
||||
<th scope="col" className="relative px-6 py-3">
|
||||
<span className="sr-only">Edit</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{props.types.map((eventType) =>
|
||||
<tr key={eventType.id}>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 align-top">
|
||||
{eventType.title}
|
||||
{eventType.hidden &&
|
||||
<span className="ml-2 inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-800">
|
||||
Hidden
|
||||
</span>
|
||||
}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-sm text-gray-500 align-top">
|
||||
{eventType.description}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 align-top">
|
||||
{eventType.length} minutes
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium align-top">
|
||||
<Link href={"/" + props.user.username + "/" + eventType.slug}><a target="_blank" className="text-blue-600 hover:text-blue-900 mr-2">View</a></Link>
|
||||
<Link href={"/availability/event/" + eventType.id}><a className="text-blue-600 hover:text-blue-900">Edit</a></Link>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex">
|
||||
<div className="w-1/2 mr-2 bg-white shadow rounded-lg">
|
||||
<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
|
||||
</h3>
|
||||
<div className="mt-2 max-w-xl text-sm text-gray-500">
|
||||
<p>
|
||||
Currently, your day is set to start at {convertMinsToHrsMins(props.user.startTime)} and end at {convertMinsToHrsMins(props.user.endTime)}.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<button onClick={toggleChangeTimesModal} type="button" className="btn btn-primary">
|
||||
Change available times
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-1/2 ml-2 bg-white shadow rounded-lg">
|
||||
<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?
|
||||
</h3>
|
||||
<div className="mt-2 max-w-xl text-sm text-gray-500">
|
||||
<p>
|
||||
Troubleshoot your availability to explore why your times are showing as they are.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<Link href="/availability/troubleshoot">
|
||||
<a className="btn btn-primary">
|
||||
Launch troubleshooter
|
||||
</a>
|
||||
</Link>
|
||||
</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">
|
||||
<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-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-lg sm:w-full sm:p-6">
|
||||
<div className="sm:flex sm:items-start mb-4">
|
||||
<div className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<PlusIcon className="h-6 w-6 text-blue-600" />
|
||||
</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 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>
|
||||
</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-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" 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-md 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}/{props.user.username}/
|
||||
</span>
|
||||
<input
|
||||
ref={slugRef}
|
||||
type="text"
|
||||
name="slug"
|
||||
id="slug"
|
||||
required
|
||||
className="flex-1 block w-full focus:ring-blue-500 focus:border-blue-500 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-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" 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-md shadow-sm">
|
||||
<input ref={lengthRef} type="number" name="length" id="length" required className="focus:ring-blue-500 focus:border-blue-500 block w-full pr-20 sm:text-sm border-gray-300 rounded-md" 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="my-8">
|
||||
<div className="relative flex items-start">
|
||||
<div className="flex items-center h-5">
|
||||
<input
|
||||
ref={isHiddenRef}
|
||||
id="ishidden"
|
||||
name="ishidden"
|
||||
type="checkbox"
|
||||
className="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded"
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3 text-sm">
|
||||
<label htmlFor="ishidden" className="font-medium text-gray-700">
|
||||
Hide this event type
|
||||
</label>
|
||||
<p className="text-gray-500">Hide the event type from your page, so it can only be booked through it's URL.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* TODO: Add an error message when required input fields empty*/}
|
||||
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<button type="submit" className="btn btn-primary">
|
||||
Create
|
||||
</button>
|
||||
<button onClick={toggleAddModal} type="button" className="btn btn-white mr-2">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{showChangeTimesModal &&
|
||||
<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-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-lg sm:w-full sm:p-6">
|
||||
<div className="sm:flex sm:items-start mb-4">
|
||||
<div className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<ClockIcon className="h-6 w-6 text-blue-600" />
|
||||
</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">
|
||||
Change your available times
|
||||
</h3>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">
|
||||
Set the start and end time of your day and a minimum buffer between your meetings.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form onSubmit={updateStartEndTimesHandler}>
|
||||
<div className="flex mb-4">
|
||||
<label className="w-1/4 pt-2 block text-sm font-medium text-gray-700">Start time</label>
|
||||
<div>
|
||||
<label htmlFor="hours" className="sr-only">Hours</label>
|
||||
<input ref={startHoursRef} type="number" name="hours" id="hours" className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder="9" defaultValue={convertMinsToHrsMins(props.user.startTime).split(":")[0]} />
|
||||
</div>
|
||||
<span className="mx-2 pt-1">:</span>
|
||||
<div>
|
||||
<label htmlFor="minutes" className="sr-only">Minutes</label>
|
||||
<input ref={startMinsRef} type="number" name="minutes" id="minutes" className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder="30" defaultValue={convertMinsToHrsMins(props.user.startTime).split(":")[1]} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex mb-4">
|
||||
<label className="w-1/4 pt-2 block text-sm font-medium text-gray-700">End time</label>
|
||||
<div>
|
||||
<label htmlFor="hours" className="sr-only">Hours</label>
|
||||
<input ref={endHoursRef} type="number" name="hours" id="hours" className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder="17" defaultValue={convertMinsToHrsMins(props.user.endTime).split(":")[0]} />
|
||||
</div>
|
||||
<span className="mx-2 pt-1">:</span>
|
||||
<div>
|
||||
<label htmlFor="minutes" className="sr-only">Minutes</label>
|
||||
<input ref={endMinsRef} type="number" name="minutes" id="minutes" className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder="30" defaultValue={convertMinsToHrsMins(props.user.endTime).split(":")[1]} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex mb-4">
|
||||
<label className="w-1/4 pt-2 block text-sm font-medium text-gray-700">Buffer</label>
|
||||
<div>
|
||||
<label htmlFor="hours" className="sr-only">Hours</label>
|
||||
<input ref={bufferHoursRef} type="number" name="hours" id="hours" className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder="0" defaultValue={convertMinsToHrsMins(props.user.bufferTime).split(":")[0]} />
|
||||
</div>
|
||||
<span className="mx-2 pt-1">:</span>
|
||||
<div>
|
||||
<label htmlFor="minutes" className="sr-only">Minutes</label>
|
||||
<input ref={bufferMinsRef} type="number" name="minutes" id="minutes" className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder="10" defaultValue={convertMinsToHrsMins(props.user.bufferTime).split(":")[1]} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<button type="submit" className="btn btn-primary">
|
||||
Update
|
||||
</button>
|
||||
<button onClick={toggleChangeTimesModal} type="button" className="btn btn-white mr-2">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<Modal heading="Start and end times changed" description="The start and end times for your day have been changed successfully." open={successModalOpen} handleClose={closeSuccessModal} />
|
||||
</Shell>
|
||||
<div className="w-1/2 ml-2 bg-white shadow 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?
|
||||
</h3>
|
||||
<div className="mt-2 max-w-xl text-sm text-gray-500">
|
||||
<p>Troubleshoot your availability to explore why your times are showing as they are.</p>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<Link href="/availability/troubleshoot">
|
||||
<a className="btn btn-primary">Launch troubleshooter</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
{showChangeTimesModal && (
|
||||
<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="sm:flex sm:items-start mb-4">
|
||||
<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">
|
||||
<ClockIcon className="h-6 w-6 text-neutral-600" />
|
||||
</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">
|
||||
Change your available times
|
||||
</h3>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">
|
||||
Set the start and end time of your day and a minimum buffer between your meetings.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form onSubmit={updateStartEndTimesHandler}>
|
||||
<div className="flex mb-4">
|
||||
<label className="w-1/4 pt-2 block text-sm font-medium text-gray-700">Start time</label>
|
||||
<div>
|
||||
<label htmlFor="hours" className="sr-only">
|
||||
Hours
|
||||
</label>
|
||||
<input
|
||||
ref={startHoursRef}
|
||||
type="number"
|
||||
name="hours"
|
||||
id="hours"
|
||||
className="shadow-sm focus:ring-neutral-500 focus:border-neutral-500 block w-full sm:text-sm border-gray-300 rounded-sm"
|
||||
placeholder="9"
|
||||
defaultValue={convertMinsToHrsMins(props.user.startTime).split(":")[0]}
|
||||
/>
|
||||
</div>
|
||||
<span className="mx-2 pt-1">:</span>
|
||||
<div>
|
||||
<label htmlFor="minutes" className="sr-only">
|
||||
Minutes
|
||||
</label>
|
||||
<input
|
||||
ref={startMinsRef}
|
||||
type="number"
|
||||
name="minutes"
|
||||
id="minutes"
|
||||
className="shadow-sm focus:ring-neutral-500 focus:border-neutral-500 block w-full sm:text-sm border-gray-300 rounded-sm"
|
||||
placeholder="30"
|
||||
defaultValue={convertMinsToHrsMins(props.user.startTime).split(":")[1]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex mb-4">
|
||||
<label className="w-1/4 pt-2 block text-sm font-medium text-gray-700">End time</label>
|
||||
<div>
|
||||
<label htmlFor="hours" className="sr-only">
|
||||
Hours
|
||||
</label>
|
||||
<input
|
||||
ref={endHoursRef}
|
||||
type="number"
|
||||
name="hours"
|
||||
id="hours"
|
||||
className="shadow-sm focus:ring-neutral-500 focus:border-neutral-500 block w-full sm:text-sm border-gray-300 rounded-sm"
|
||||
placeholder="17"
|
||||
defaultValue={convertMinsToHrsMins(props.user.endTime).split(":")[0]}
|
||||
/>
|
||||
</div>
|
||||
<span className="mx-2 pt-1">:</span>
|
||||
<div>
|
||||
<label htmlFor="minutes" className="sr-only">
|
||||
Minutes
|
||||
</label>
|
||||
<input
|
||||
ref={endMinsRef}
|
||||
type="number"
|
||||
name="minutes"
|
||||
id="minutes"
|
||||
className="shadow-sm focus:ring-neutral-500 focus:border-neutral-500 block w-full sm:text-sm border-gray-300 rounded-sm"
|
||||
placeholder="30"
|
||||
defaultValue={convertMinsToHrsMins(props.user.endTime).split(":")[1]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex mb-4">
|
||||
<label className="w-1/4 pt-2 block text-sm font-medium text-gray-700">Buffer</label>
|
||||
<div>
|
||||
<label htmlFor="hours" className="sr-only">
|
||||
Hours
|
||||
</label>
|
||||
<input
|
||||
ref={bufferHoursRef}
|
||||
type="number"
|
||||
name="hours"
|
||||
id="hours"
|
||||
className="shadow-sm focus:ring-neutral-500 focus:border-neutral-500 block w-full sm:text-sm border-gray-300 rounded-sm"
|
||||
placeholder="0"
|
||||
defaultValue={convertMinsToHrsMins(props.user.bufferTime).split(":")[0]}
|
||||
/>
|
||||
</div>
|
||||
<span className="mx-2 pt-1">:</span>
|
||||
<div>
|
||||
<label htmlFor="minutes" className="sr-only">
|
||||
Minutes
|
||||
</label>
|
||||
<input
|
||||
ref={bufferMinsRef}
|
||||
type="number"
|
||||
name="minutes"
|
||||
id="minutes"
|
||||
className="shadow-sm focus:ring-neutral-500 focus:border-neutral-500 block w-full sm:text-sm border-gray-300 rounded-sm"
|
||||
placeholder="10"
|
||||
defaultValue={convertMinsToHrsMins(props.user.bufferTime).split(":")[1]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<button type="submit" className="btn btn-primary">
|
||||
Update
|
||||
</button>
|
||||
<button onClick={toggleChangeTimesModal} type="button" className="btn btn-white mr-2">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Modal
|
||||
heading="Start and end times changed"
|
||||
description="The start and end times for your day have been changed successfully."
|
||||
open={successModalOpen}
|
||||
handleClose={closeSuccessModal}
|
||||
/>
|
||||
</Shell>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
const session = await getSession(context);
|
||||
if (!session) {
|
||||
return { redirect: { permanent: false, destination: '/auth/login' } };
|
||||
}
|
||||
const session = await getSession(context);
|
||||
if (!session) {
|
||||
return { redirect: { permanent: false, destination: "/auth/login" } };
|
||||
}
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: session.user.email,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
bufferTime: true
|
||||
}
|
||||
});
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: session.user.email,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
bufferTime: true,
|
||||
},
|
||||
});
|
||||
|
||||
const types = await prisma.eventType.findMany({
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
slug: true,
|
||||
description: true,
|
||||
length: true,
|
||||
hidden: true
|
||||
}
|
||||
});
|
||||
return {
|
||||
props: {user, types}, // will be passed to the page component as props
|
||||
}
|
||||
const types = await prisma.eventType.findMany({
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
slug: true,
|
||||
description: true,
|
||||
length: true,
|
||||
hidden: true,
|
||||
},
|
||||
});
|
||||
return {
|
||||
props: { user, types }, // will be passed to the page component as props
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import dayjs from "dayjs";
|
|||
import utc from "dayjs/plugin/utc";
|
||||
import { GetServerSideProps } from "next";
|
||||
import prisma from "@lib/prisma";
|
||||
import Loader from '@components/Loader';
|
||||
dayjs.extend(utc);
|
||||
|
||||
export default function Troubleshoot({ user }) {
|
||||
|
@ -14,7 +15,7 @@ export default function Troubleshoot({ user }) {
|
|||
const [selectedDate, setSelectedDate] = useState(dayjs());
|
||||
|
||||
if (loading) {
|
||||
return <div className="loader"></div>;
|
||||
return <Loader/>;
|
||||
}
|
||||
|
||||
function convertMinsToHrsMins(mins) {
|
||||
|
@ -50,26 +51,38 @@ export default function Troubleshoot({ user }) {
|
|||
<title>Troubleshoot | Calendso</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<Shell heading="Troubleshoot">
|
||||
<div className="bg-white overflow-hidden shadow rounded-lg">
|
||||
<Shell
|
||||
heading="Troubleshoot"
|
||||
subtitle="Understand why certain times are available and others are blocked.">
|
||||
<div className="bg-white max-w-md overflow-hidden shadow rounded-sm">
|
||||
<div className="px-4 py-5 sm:p-6">
|
||||
Here is an overview of your day on {selectedDate.format("D MMMM YYYY")}:
|
||||
<small className="block text-gray-400">Tip: Hover over the bold times for a full timestamp</small>
|
||||
<small className="block text-neutral-400">
|
||||
Tip: Hover over the bold times for a full timestamp
|
||||
</small>
|
||||
<div className="mt-4 space-y-4">
|
||||
<div className="bg-gray-600 overflow-hidden rounded-lg">
|
||||
<div className="bg-black overflow-hidden rounded-sm">
|
||||
<div className="px-4 sm:px-6 py-2 text-white">
|
||||
Your day starts at {convertMinsToHrsMins(user.startTime)}
|
||||
</div>
|
||||
</div>
|
||||
{availability.map((slot) => (
|
||||
<div key={slot.start} className="bg-gray-100 overflow-hidden rounded-lg">
|
||||
<div className="px-4 py-5 sm:p-6 text-gray-600">
|
||||
Your calendar shows you as busy between <span className="font-medium text-gray-800" title={slot.start}>{dayjs(slot.start).format("HH:mm")}</span> and <span className="font-medium text-gray-800" title={slot.end}>{dayjs(slot.end).format("HH:mm")}</span> on {dayjs(slot.start).format("D MMMM YYYY")}
|
||||
<div key={slot.start} className="bg-neutral-100 overflow-hidden rounded-sm">
|
||||
<div className="px-4 py-5 sm:p-6 text-black">
|
||||
Your calendar shows you as busy between{" "}
|
||||
<span className="font-medium text-neutral-800" title={slot.start}>
|
||||
{dayjs(slot.start).format("HH:mm")}
|
||||
</span>{" "}
|
||||
and{" "}
|
||||
<span className="font-medium text-neutral-800" title={slot.end}>
|
||||
{dayjs(slot.end).format("HH:mm")}
|
||||
</span>{" "}
|
||||
on {dayjs(slot.start).format("D MMMM YYYY")}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{availability.length === 0 && <div className="loader"></div>}
|
||||
<div className="bg-gray-600 overflow-hidden rounded-lg">
|
||||
{availability.length === 0 && <Loader />}
|
||||
<div className="bg-black overflow-hidden rounded-sm">
|
||||
<div className="px-4 sm:px-6 py-2 text-white">
|
||||
Your day ends at {convertMinsToHrsMins(user.endTime)}
|
||||
</div>
|
||||
|
|
|
@ -33,11 +33,11 @@ export default function Bookings({ bookings }) {
|
|||
<title>Bookings | Calendso</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<Shell heading="Bookings">
|
||||
<div className="flex flex-col">
|
||||
<Shell heading="Bookings" subtitle="See upcoming and past events booked through your event type links.">
|
||||
<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-lg">
|
||||
<div className="shadow overflow-hidden border-b border-gray-200 sm:rounded-sm">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
|
@ -67,12 +67,9 @@ export default function Bookings({ bookings }) {
|
|||
.concat(bookings.filter((booking) => booking.confirmed || booking.rejected))
|
||||
.map((booking) => (
|
||||
<tr key={booking.id}>
|
||||
<td
|
||||
className={
|
||||
"px-6 py-4 whitespace-nowrap" + (booking.rejected ? " line-through" : "")
|
||||
}>
|
||||
<td className={"px-6 py-4" + (booking.rejected ? " line-through" : "")}>
|
||||
{!booking.confirmed && !booking.rejected && (
|
||||
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-600 text-gray-100">
|
||||
<span className="ml-2 inline-flex items-center px-1.5 py-0.5 rounded-sm text-xs font-medium bg-yellow-100 text-yellow-800">
|
||||
Unconfirmed
|
||||
</span>
|
||||
)}
|
||||
|
@ -80,13 +77,22 @@ export default function Bookings({ bookings }) {
|
|||
{booking.attendees[0].name}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">{booking.attendees[0].email}</div>
|
||||
<div
|
||||
style={{ maxWidth: 150 }}
|
||||
className="block lg:hidden font-medium text-xs text-gray-900 truncate">
|
||||
{booking.title}
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
className={
|
||||
"px-6 py-4 max-w-20 w-full" + (booking.rejected ? " line-through" : "")
|
||||
}>
|
||||
<div className="text-sm text-gray-900">{booking.title}</div>
|
||||
<div className="text-sm text-gray-500">{booking.description}</div>
|
||||
<div className="hidden lg:block text-sm text-neutral-900 font-medium">
|
||||
{booking.title}
|
||||
</div>
|
||||
<div className="hidden lg:block text-sm text-neutral-500">
|
||||
You and {booking.attendees[0].name}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="text-sm text-gray-900">
|
||||
|
@ -99,29 +105,29 @@ export default function Bookings({ bookings }) {
|
|||
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
{!booking.confirmed && !booking.rejected && (
|
||||
<>
|
||||
<a
|
||||
<button
|
||||
onClick={() => confirmBookingHandler(booking, true)}
|
||||
className="cursor-pointer text-blue-600 hover:text-blue-900">
|
||||
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">
|
||||
Confirm
|
||||
</a>
|
||||
<a
|
||||
</button>
|
||||
<button
|
||||
onClick={() => confirmBookingHandler(booking, false)}
|
||||
className="cursor-pointer ml-4 text-blue-600 hover:text-blue-900">
|
||||
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">
|
||||
Reject
|
||||
</a>
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
{booking.confirmed && !booking.rejected && (
|
||||
<>
|
||||
<a
|
||||
href={window.location.href + "/../reschedule/" + booking.uid}
|
||||
className="text-blue-600 hover:text-blue-900">
|
||||
Reschedule
|
||||
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">
|
||||
Cancel
|
||||
</a>
|
||||
<a
|
||||
href={window.location.href + "/../cancel/" + booking.uid}
|
||||
className="ml-4 text-blue-600 hover:text-blue-900">
|
||||
Cancel
|
||||
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">
|
||||
Reschedule
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
|
|
1197
pages/event-types/[type].tsx
Normal file
662
pages/event-types/index.tsx
Normal file
|
@ -0,0 +1,662 @@
|
|||
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 { Menu, Transition } from "@headlessui/react";
|
||||
|
||||
function classNames(...classes) {
|
||||
return classes.filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
import {
|
||||
ClockIcon,
|
||||
DotsHorizontalIcon,
|
||||
ExternalLinkIcon,
|
||||
InformationCircleIcon,
|
||||
LinkIcon,
|
||||
PlusIcon,
|
||||
UserIcon,
|
||||
} from "@heroicons/react/solid";
|
||||
import Loader from '@components/Loader';
|
||||
|
||||
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>();
|
||||
const descriptionRef = useRef<HTMLTextAreaElement>();
|
||||
const lengthRef = useRef<HTMLInputElement>();
|
||||
|
||||
async function createEventTypeHandler(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const enteredTitle = titleRef.current.value;
|
||||
const enteredSlug = slugRef.current.value;
|
||||
const enteredDescription = descriptionRef.current.value;
|
||||
const enteredLength = lengthRef.current.value;
|
||||
|
||||
// TODO: Add validation
|
||||
|
||||
const response = await fetch("/api/availability/eventtype", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
title: enteredTitle,
|
||||
slug: enteredSlug,
|
||||
description: enteredDescription,
|
||||
length: enteredLength,
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (enteredTitle && enteredLength) {
|
||||
router.replace(router.asPath);
|
||||
toggleAddModal();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleAddModal() {
|
||||
setShowAddModal(!showAddModal);
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return <Loader/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Head>
|
||||
<title>Event Types | Calendso</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<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">
|
||||
<ul className="divide-y divide-neutral-200">
|
||||
{types.map((type) => (
|
||||
<li key={type.id}>
|
||||
<Link href={"/event-types/" + type.id}>
|
||||
<a className="block hover:bg-neutral-50">
|
||||
<div className="px-4 py-4 flex items-center sm:px-6">
|
||||
<div className="min-w-0 flex-1 sm:flex sm:items-center sm:justify-between">
|
||||
<div className="truncate">
|
||||
<div className="flex text-sm">
|
||||
<p className="font-medium text-neutral-900 truncate">{type.title}</p>
|
||||
{type.hidden && (
|
||||
<span className="ml-2 inline-flex items-center px-1.5 py-0.5 rounded-sm text-xs font-medium bg-yellow-100 text-yellow-800">
|
||||
Hidden
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<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"
|
||||
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"
|
||||
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"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<p>{type.description.substring(0, 100)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 flex-shrink-0 sm:mt-0 sm:ml-5">
|
||||
<div className="flex overflow-hidden space-x-5">
|
||||
<Link href={"/" + session.user.username + "/" + type.slug}>
|
||||
<a className="text-neutral-400">
|
||||
<ExternalLinkIcon className="w-5 h-5" />
|
||||
</a>
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
window.location.hostname + "/" + session.user.username + "/" + type.slug
|
||||
);
|
||||
}}
|
||||
className="text-neutral-400">
|
||||
<LinkIcon className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-5 flex-shrink-0">
|
||||
<Menu as="div" className="inline-block text-left">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<div>
|
||||
<Menu.Button className="text-neutral-400 mt-1">
|
||||
<span className="sr-only">Open options</span>
|
||||
<DotsHorizontalIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</Menu.Button>
|
||||
</div>
|
||||
|
||||
<Transition
|
||||
show={open}
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95">
|
||||
<Menu.Items
|
||||
static
|
||||
className="origin-top-right absolute right-0 mt-2 w-56 rounded-sm shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none divide-y divide-neutral-100">
|
||||
<div className="py-1">
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<a
|
||||
href={"/" + session.user.username + "/" + type.slug}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={classNames(
|
||||
active ? "bg-neutral-100 text-neutral-900" : "text-neutral-700",
|
||||
"group flex items-center px-4 py-2 text-sm font-medium"
|
||||
)}>
|
||||
<ExternalLinkIcon
|
||||
className="mr-3 h-5 w-5 text-neutral-400 group-hover:text-neutral-500"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Preview
|
||||
</a>
|
||||
)}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<button
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
window.location.hostname +
|
||||
"/" +
|
||||
session.user.username +
|
||||
"/" +
|
||||
type.slug
|
||||
);
|
||||
}}
|
||||
className={classNames(
|
||||
active ? "bg-neutral-100 text-neutral-900" : "text-neutral-700",
|
||||
"group flex items-center px-4 py-2 text-sm w-full font-medium"
|
||||
)}>
|
||||
<LinkIcon
|
||||
className="mr-3 h-5 w-5 text-neutral-400 group-hover:text-neutral-500"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Copy link to event
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
{/*<Menu.Item>*/}
|
||||
{/* {({ active }) => (*/}
|
||||
{/* <a*/}
|
||||
{/* href="#"*/}
|
||||
{/* className={classNames(*/}
|
||||
{/* active ? "bg-neutral-100 text-neutral-900" : "text-neutral-700",*/}
|
||||
{/* "group flex items-center px-4 py-2 text-sm font-medium"*/}
|
||||
{/* )}>*/}
|
||||
{/* <DuplicateIcon*/}
|
||||
{/* className="mr-3 h-5 w-5 text-neutral-400 group-hover:text-neutral-500"*/}
|
||||
{/* aria-hidden="true"*/}
|
||||
{/* />*/}
|
||||
{/* Duplicate*/}
|
||||
{/* </a>*/}
|
||||
{/* )}*/}
|
||||
{/*</Menu.Item>*/}
|
||||
</div>
|
||||
{/*<div className="py-1">*/}
|
||||
{/* <Menu.Item>*/}
|
||||
{/* {({ active }) => (*/}
|
||||
{/* <a*/}
|
||||
{/* href="#"*/}
|
||||
{/* className={classNames(*/}
|
||||
{/* active ? "bg-red-100 text-red-900" : "text-red-700",*/}
|
||||
{/* "group flex items-center px-4 py-2 text-sm font-medium"*/}
|
||||
{/* )}>*/}
|
||||
{/* <TrashIcon*/}
|
||||
{/* className="mr-3 h-5 w-5 text-red-400 group-hover:text-red-700"*/}
|
||||
{/* aria-hidden="true"*/}
|
||||
{/* />*/}
|
||||
{/* Delete*/}
|
||||
{/* </a>*/}
|
||||
{/* )}*/}
|
||||
{/* </Menu.Item>*/}
|
||||
{/*</div>*/}
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
{types.length === 0 && (
|
||||
<div className="text-center max-w-lg mx-auto">
|
||||
<svg
|
||||
className="mx-auto mb-4 w-32 h-32"
|
||||
viewBox="0 0 132 132"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<rect
|
||||
x="1.48387"
|
||||
y="1.48387"
|
||||
width="129.032"
|
||||
height="129.032"
|
||||
rx="64.5161"
|
||||
fill="white"
|
||||
stroke="white"
|
||||
strokeWidth="1.03226"
|
||||
/>
|
||||
<mask
|
||||
id="mask0"
|
||||
mask-type="alpha"
|
||||
maskUnits="userSpaceOnUse"
|
||||
x="2"
|
||||
y="2"
|
||||
width="128"
|
||||
height="128">
|
||||
<rect x="2" y="2" width="128" height="128" rx="64" fill="white" />
|
||||
</mask>
|
||||
<g mask="url(#mask0)">
|
||||
<rect x="56.1936" y="40.1936" width="20.129" height="2.06452" rx="0.516129" fill="#708097" />
|
||||
<rect x="47.9355" y="44.8387" width="36.6452" height="2.06452" rx="0.516129" fill="#C6CCD5" />
|
||||
<g filter="url(#filter0_dd)">
|
||||
<rect width="115.84" height="83.2303" transform="translate(8.07983 53.52)" fill="#F7F8F9" />
|
||||
<path
|
||||
d="M15.7699 61.589V63.5013H16.1023V62.1847H16.1201L16.6486 63.4957H16.8969L17.4254 62.1875H17.4432V63.5013H17.7756V61.589H17.3517L16.7839 62.9747H16.7615L16.1938 61.589H15.7699ZM19.993 62.5451C19.993 61.927 19.6158 61.5628 19.1144 61.5628C18.612 61.5628 18.2357 61.927 18.2357 62.5451C18.2357 63.1623 18.612 63.5274 19.1144 63.5274C19.6158 63.5274 19.993 63.1633 19.993 62.5451ZM19.6447 62.5451C19.6447 62.9803 19.4262 63.2165 19.1144 63.2165C18.8034 63.2165 18.584 62.9803 18.584 62.5451C18.584 62.11 18.8034 61.8738 19.1144 61.8738C19.4262 61.8738 19.6447 62.11 19.6447 62.5451Z"
|
||||
fill="#657388"
|
||||
/>
|
||||
<path
|
||||
d="M32.1112 61.8794H32.7022V63.5013H33.0459V61.8794H33.6369V61.589H32.1112V61.8794ZM35.268 61.589V62.8094C35.268 63.0494 35.1008 63.2212 34.8385 63.2212C34.5751 63.2212 34.4089 63.0494 34.4089 62.8094V61.589H34.0625V62.8383C34.0625 63.2492 34.3706 63.5302 34.8385 63.5302C35.3044 63.5302 35.6144 63.2492 35.6144 62.8383V61.589H35.268Z"
|
||||
fill="#657388"
|
||||
/>
|
||||
<path
|
||||
d="M48.3554 63.5013H48.6971L49.0809 62.1595H49.0958L49.4786 63.5013H49.8204L50.3601 61.589H49.9875L49.643 62.9952H49.6262L49.2573 61.589H48.9184L48.5505 62.9943H48.5328L48.1882 61.589H47.8157L48.3554 63.5013ZM50.7318 63.5013H51.983V63.2109H51.0782V62.6889H51.9111V62.3985H51.0782V61.8794H51.9755V61.589H50.7318V63.5013Z"
|
||||
fill="#657388"
|
||||
/>
|
||||
<path
|
||||
d="M64.1925 61.8794H64.7836V63.5013H65.1272V61.8794H65.7183V61.589H64.1925V61.8794ZM66.1439 63.5013H66.4903V62.6889H67.3764V63.5013H67.7237V61.589H67.3764V62.3985H66.4903V61.589H66.1439V63.5013Z"
|
||||
fill="#657388"
|
||||
/>
|
||||
<path
|
||||
d="M80.545 63.5013H80.8914V62.6889H81.686V62.3985H80.8914V61.8794H81.7701V61.589H80.545V63.5013ZM82.2171 63.5013H82.5636V62.801H82.9165L83.2919 63.5013H83.6784L83.2648 62.7431C83.4898 62.6525 83.6084 62.4602 83.6084 62.2006C83.6084 61.8355 83.3731 61.589 82.9342 61.589H82.2171V63.5013ZM82.5636 62.5134V61.8784H82.881C83.1397 61.8784 83.2555 61.997 83.2555 62.2006C83.2555 62.4041 83.1397 62.5134 82.8829 62.5134H82.5636Z"
|
||||
fill="#657388"
|
||||
/>
|
||||
<path
|
||||
d="M97.4678 62.1147H97.8011C97.7946 61.7916 97.5191 61.5628 97.112 61.5628C96.7105 61.5628 96.4089 61.7888 96.4098 62.1268C96.4098 62.4013 96.605 62.5591 96.9197 62.6404L97.1372 62.6964C97.3436 62.7487 97.4799 62.8131 97.4808 62.9616C97.4799 63.125 97.3249 63.2342 97.0989 63.2342C96.8823 63.2342 96.7142 63.1371 96.7002 62.9364H96.3594C96.3734 63.3164 96.6563 63.5302 97.1017 63.5302C97.5602 63.5302 97.8263 63.3015 97.8273 62.9644C97.8263 62.6329 97.5527 62.4816 97.2651 62.4135L97.0859 62.3687C96.929 62.3313 96.7591 62.265 96.7609 62.1053C96.7619 61.9615 96.8907 61.856 97.1073 61.856C97.3137 61.856 97.45 61.9522 97.4678 62.1147ZM98.4823 63.5013L98.6401 63.0297H99.3591L99.5178 63.5013H99.8876L99.2134 61.589H98.7858L98.1126 63.5013H98.4823ZM98.7335 62.7515L98.9921 61.9812H99.0071L99.2657 62.7515H98.7335Z"
|
||||
fill="#657388"
|
||||
/>
|
||||
<path
|
||||
d="M113.487 62.1147H113.82C113.814 61.7916 113.538 61.5628 113.131 61.5628C112.73 61.5628 112.428 61.7888 112.429 62.1268C112.429 62.4013 112.624 62.5591 112.939 62.6404L113.157 62.6964C113.363 62.7487 113.499 62.8131 113.5 62.9616C113.499 63.125 113.344 63.2342 113.118 63.2342C112.902 63.2342 112.734 63.1371 112.72 62.9364H112.379C112.393 63.3164 112.676 63.5302 113.121 63.5302C113.58 63.5302 113.846 63.3015 113.847 62.9644C113.846 62.6329 113.572 62.4816 113.285 62.4135L113.105 62.3687C112.948 62.3313 112.778 62.265 112.78 62.1053C112.781 61.9615 112.91 61.856 113.127 61.856C113.333 61.856 113.469 61.9522 113.487 62.1147ZM115.492 61.589V62.8094C115.492 63.0494 115.325 63.2212 115.063 63.2212C114.8 63.2212 114.633 63.0494 114.633 62.8094V61.589H114.287V62.8383C114.287 63.2492 114.595 63.5302 115.063 63.5302C115.529 63.5302 115.839 63.2492 115.839 62.8383V61.589H115.492Z"
|
||||
fill="#657388"
|
||||
/>
|
||||
<rect x="9.83276" y="70.2902" width="112.334" height="0.516129" fill="white" />
|
||||
<path
|
||||
d="M66.3454 77.5155H66.0366L65.3992 77.9388V78.2525L66.0217 77.8392H66.0366V80.0652H66.3454V77.5155Z"
|
||||
fill="#9BA6B6"
|
||||
/>
|
||||
<path
|
||||
d="M81.2501 80.0652H82.8586V79.7913H81.6734V79.7714L82.2461 79.1588C82.6843 78.6895 82.8138 78.4704 82.8138 78.1877C82.8138 77.7943 82.4951 77.4806 82.0469 77.4806C81.6 77.4806 81.2601 77.7844 81.2601 78.2326H81.5539C81.5539 77.9425 81.7419 77.7495 82.0369 77.7495C82.3133 77.7495 82.525 77.9188 82.525 78.1877C82.525 78.4231 82.3868 78.5973 82.0917 78.9198L81.2501 79.8411V80.0652Z"
|
||||
fill="#9BA6B6"
|
||||
/>
|
||||
<path
|
||||
d="M98.1047 80.1C98.599 80.1 98.9662 79.79 98.9662 79.373C98.9662 79.0493 98.7745 78.814 98.4533 78.7604V78.7405C98.711 78.6621 98.8716 78.4504 98.8716 78.1628C98.8716 77.8018 98.5865 77.4806 98.1147 77.4806C97.6739 77.4806 97.3079 77.752 97.293 78.1529H97.5918C97.603 77.8989 97.8445 77.7495 98.1097 77.7495C98.3911 77.7495 98.5728 77.9201 98.5728 78.1778C98.5728 78.4467 98.3624 78.621 98.0599 78.621H97.8557V78.8949H98.0599C98.4471 78.8949 98.6625 79.0916 98.6625 79.373C98.6625 79.6431 98.4272 79.8261 98.0997 79.8261C97.8047 79.8261 97.5706 79.6743 97.5519 79.4278H97.2382C97.2569 79.8286 97.6105 80.1 98.1047 80.1Z"
|
||||
fill="#9BA6B6"
|
||||
/>
|
||||
<path
|
||||
d="M113.222 79.5423H114.423V80.0652H114.716V79.5423H115.065V79.2684H114.716V77.5155H114.343L113.222 79.2883V79.5423ZM114.423 79.2684H113.556V79.2485L114.403 77.9089H114.423V79.2684Z"
|
||||
fill="#9BA6B6"
|
||||
/>
|
||||
<path
|
||||
d="M17.8508 96.1477C18.3364 96.1477 18.6925 95.7892 18.6925 95.3011C18.6925 94.8069 18.3488 94.4446 17.8807 94.4446C17.7089 94.4446 17.5421 94.5056 17.4375 94.589H17.4226L17.5122 93.837H18.5779V93.5631H17.2533L17.0989 94.8181L17.3877 94.8529C17.4935 94.777 17.6741 94.7222 17.8309 94.7235C18.1559 94.7259 18.3937 94.9724 18.3937 95.3061C18.3937 95.6335 18.1646 95.8738 17.8508 95.8738C17.5894 95.8738 17.3815 95.7057 17.3578 95.4754H17.059C17.0777 95.8639 17.4126 96.1477 17.8508 96.1477Z"
|
||||
fill="#9BA6B6"
|
||||
/>
|
||||
<rect x="26.3188" y="87.2924" width="15.1717" height="15.1717" fill="#DBEAFE" />
|
||||
<path
|
||||
d="M33.9284 96.1478C34.4786 96.1516 34.8484 95.7731 34.8472 95.2689C34.8484 94.7871 34.5048 94.4385 34.0578 94.4385C33.7839 94.4385 33.5424 94.5717 33.4204 94.7908H33.403C33.4042 94.2542 33.6009 93.928 33.9545 93.928C34.1736 93.928 34.3218 94.055 34.3691 94.2505H34.8235C34.7687 93.8384 34.4363 93.5284 33.9545 93.5284C33.342 93.5284 32.9548 94.0388 32.9548 94.9103C32.9535 95.8453 33.4391 96.1453 33.9284 96.1478ZM33.9259 95.7743C33.6532 95.7743 33.454 95.549 33.4528 95.2826C33.4553 95.0149 33.6619 94.7908 33.9321 94.7908C34.2023 94.7908 34.4002 95.0049 34.399 95.2788C34.4002 95.5577 34.196 95.7743 33.9259 95.7743Z"
|
||||
fill="#3B82F6"
|
||||
/>
|
||||
<circle cx="34.1386" cy="99.9637" r="0.657352" fill="#3B82F6" />
|
||||
<rect x="42.3665" y="87.2924" width="15.1717" height="15.1717" fill="#DBEAFE" />
|
||||
<path
|
||||
d="M49.2434 96.113H49.7228L50.8059 93.9579V93.5632H49.0691V93.9492H50.3278V93.9666L49.2434 96.113Z"
|
||||
fill="#3B82F6"
|
||||
/>
|
||||
<rect x="58.4143" y="87.2924" width="15.1717" height="15.1717" fill="#DBEAFE" />
|
||||
<path
|
||||
d="M66.0013 96.1478C66.5528 96.1478 66.9475 95.8441 66.9487 95.4295C66.9475 95.1108 66.7122 94.8443 66.4159 94.7945V94.7771C66.6736 94.7198 66.8529 94.4883 66.8541 94.2119C66.8529 93.8197 66.4918 93.5284 66.0013 93.5284C65.5071 93.5284 65.146 93.8185 65.1473 94.2119C65.146 94.4883 65.3228 94.7198 65.5855 94.7771V94.7945C65.2842 94.8443 65.0514 95.1108 65.0526 95.4295C65.0514 95.8441 65.4448 96.1478 66.0013 96.1478ZM66.0013 95.7918C65.7125 95.7918 65.5257 95.6324 65.5282 95.3971C65.5257 95.1531 65.7262 94.98 66.0013 94.98C66.2727 94.98 66.4719 95.1543 66.4744 95.3971C66.4719 95.6324 66.2864 95.7918 66.0013 95.7918ZM66.0013 94.6302C65.7648 94.6302 65.5955 94.4771 65.5979 94.2555C65.5955 94.0363 65.7598 93.8894 66.0013 93.8894C66.2391 93.8894 66.4022 94.0363 66.4047 94.2555C66.4022 94.4783 66.2341 94.6302 66.0013 94.6302Z"
|
||||
fill="#3B82F6"
|
||||
/>
|
||||
<rect x="74.4617" y="87.2924" width="15.1717" height="15.1717" fill="#DBEAFE" />
|
||||
<path
|
||||
d="M82.0226 93.5284C81.4698 93.5247 81.1026 93.9044 81.1026 94.4024C81.1038 94.8829 81.4462 95.2303 81.8931 95.2303C82.1683 95.2303 82.4073 95.0971 82.5306 94.878H82.548C82.5468 95.4233 82.3488 95.7482 81.9965 95.7482C81.7761 95.7482 81.628 95.6212 81.5819 95.4183H81.1275C81.1798 95.8403 81.5134 96.1478 81.9965 96.1478C82.6078 96.1478 82.9974 95.6374 82.9962 94.7597C82.995 93.8309 82.5119 93.5309 82.0226 93.5284ZM82.0239 93.9019C82.2965 93.9019 82.497 94.1285 82.497 94.3887C82.4982 94.6526 82.2878 94.8792 82.0189 94.8792C81.7475 94.8792 81.552 94.6651 81.5508 94.3924C81.5508 94.1185 81.7537 93.9019 82.0239 93.9019Z"
|
||||
fill="#3B82F6"
|
||||
/>
|
||||
<rect x="90.5098" y="87.2924" width="15.1717" height="15.1717" fill="#DBEAFE" />
|
||||
<path
|
||||
d="M97.3476 93.5632H96.9081L96.2744 93.9704V94.3937L96.8708 94.0127H96.8857V96.113H97.3476V93.5632ZM98.9375 96.1615C99.5525 96.1628 99.9197 95.6772 99.9197 94.8406C99.9197 94.009 99.55 93.5284 98.9375 93.5284C98.3249 93.5284 97.9564 94.0077 97.9552 94.8406C97.9552 95.676 98.3224 96.1615 98.9375 96.1615ZM98.9375 95.7719C98.62 95.7719 98.4208 95.4531 98.422 94.8406C98.4233 94.233 98.6212 93.9131 98.9375 93.9131C99.2549 93.9131 99.4529 94.233 99.4541 94.8406C99.4541 95.4531 99.2562 95.7719 98.9375 95.7719Z"
|
||||
fill="#3B82F6"
|
||||
/>
|
||||
<path
|
||||
d="M113.674 93.5632H113.365L112.727 93.9865V94.3003L113.35 93.8869H113.365V96.113H113.674V93.5632ZM115.303 93.5632H114.995L114.357 93.9865V94.3003L114.98 93.8869H114.995V96.113H115.303V93.5632Z"
|
||||
fill="#9BA6B6"
|
||||
/>
|
||||
<rect x="10.2712" y="103.34" width="15.1717" height="15.1717" fill="#DBEAFE" />
|
||||
<path
|
||||
d="M17.1895 109.611H16.7501L16.1164 110.018V110.441L16.7127 110.06H16.7276V112.161H17.1895V109.611ZM17.8369 112.161H19.5849V111.775H18.4744V111.757L18.9138 111.31C19.4093 110.835 19.5463 110.603 19.5463 110.316C19.5463 109.889 19.1989 109.576 18.686 109.576C18.1805 109.576 17.822 109.89 17.822 110.374H18.2615C18.2615 110.114 18.4258 109.951 18.6798 109.951C18.9226 109.951 19.1031 110.099 19.1031 110.339C19.1031 110.552 18.9736 110.704 18.7221 110.959L17.8369 111.827V112.161Z"
|
||||
fill="#3B82F6"
|
||||
/>
|
||||
<rect x="26.3188" y="103.34" width="15.1717" height="15.1717" fill="#DBEAFE" />
|
||||
<path
|
||||
d="M33.1843 109.611H32.7448L32.1111 110.018V110.441L32.7075 110.06H32.7224V112.161H33.1843V109.611ZM34.7468 112.196C35.2921 112.196 35.6892 111.883 35.688 111.452C35.6892 111.134 35.49 110.905 35.1327 110.853V110.834C35.4091 110.774 35.5946 110.568 35.5934 110.282C35.5946 109.894 35.2634 109.576 34.7542 109.576C34.2587 109.576 33.8753 109.871 33.8653 110.298H34.3098C34.3173 110.084 34.5165 109.951 34.7518 109.951C34.9895 109.951 35.1477 110.095 35.1464 110.309C35.1477 110.532 34.9634 110.68 34.6995 110.68H34.4741V111.036H34.6995C35.0219 111.036 35.2136 111.198 35.2124 111.429C35.2136 111.654 35.0182 111.808 34.7455 111.808C34.4891 111.808 34.2911 111.675 34.2799 111.467H33.8118C33.8242 111.898 34.2089 112.196 34.7468 112.196Z"
|
||||
fill="#3B82F6"
|
||||
/>
|
||||
<rect x="42.3665" y="103.34" width="15.1717" height="15.1717" fill="#DBEAFE" />
|
||||
<path
|
||||
d="M49.2063 109.611H48.7668L48.1331 110.018V110.441L48.7294 110.06H48.7444V112.161H49.2063V109.611ZM49.8076 111.688H51.0239V112.161H51.4647V111.688H51.7908V111.308H51.4647V109.611H50.8895L49.8076 111.32V111.688ZM51.0289 111.308H50.2807V111.288L51.009 110.134H51.0289V111.308Z"
|
||||
fill="#3B82F6"
|
||||
/>
|
||||
<path
|
||||
d="M65.2789 109.611H64.9702L64.3327 110.034V110.348L64.9552 109.935H64.9702V112.161H65.2789V109.611ZM66.809 112.196C67.2945 112.196 67.6506 111.837 67.6506 111.349C67.6506 110.855 67.307 110.492 66.8389 110.492C66.6671 110.492 66.5002 110.553 66.3957 110.637H66.3807L66.4704 109.885H67.5361V109.611H66.2114L66.057 110.866L66.3459 110.901C66.4517 110.825 66.6322 110.77 66.7891 110.771C67.114 110.774 67.3518 111.02 67.3518 111.354C67.3518 111.681 67.1227 111.922 66.809 111.922C66.5476 111.922 66.3396 111.754 66.316 111.523H66.0172C66.0359 111.912 66.3708 112.196 66.809 112.196Z"
|
||||
fill="#9BA6B6"
|
||||
/>
|
||||
<rect x="74.4617" y="103.34" width="15.1717" height="15.1717" fill="#DBEAFE" />
|
||||
<path
|
||||
d="M81.3323 109.611H80.8928L80.2591 110.018V110.441L80.8554 110.06H80.8704V112.161H81.3323V109.611ZM82.9134 112.196C83.4637 112.199 83.8335 111.821 83.8322 111.317C83.8335 110.835 83.4898 110.486 83.0429 110.486C82.769 110.486 82.5275 110.619 82.4055 110.839H82.388C82.3893 110.302 82.586 109.976 82.9396 109.976C83.1587 109.976 83.3068 110.103 83.3541 110.298H83.8086C83.7538 109.886 83.4214 109.576 82.9396 109.576C82.327 109.576 81.9398 110.087 81.9398 110.958C81.9386 111.893 82.4241 112.193 82.9134 112.196ZM82.9109 111.822C82.6383 111.822 82.4391 111.597 82.4378 111.33C82.4403 111.063 82.647 110.839 82.9171 110.839C83.1873 110.839 83.3853 111.053 83.384 111.327C83.3853 111.605 83.1811 111.822 82.9109 111.822Z"
|
||||
fill="#3B82F6"
|
||||
/>
|
||||
<path
|
||||
d="M97.4394 109.611H97.1307L96.4932 110.034V110.348L97.1157 109.935H97.1307V112.161H97.4394V109.611ZM98.2524 112.161H98.5761L99.7115 109.9V109.611H98.0781V109.885H99.3928V109.905L98.2524 112.161Z"
|
||||
fill="#9BA6B6"
|
||||
/>
|
||||
<path
|
||||
d="M113.408 109.611H113.1L112.462 110.034V110.348L113.085 109.935H113.1V112.161H113.408V109.611ZM114.958 112.196C115.467 112.196 115.822 111.898 115.825 111.483C115.822 111.161 115.607 110.887 115.332 110.836V110.821C115.571 110.759 115.728 110.525 115.73 110.253C115.728 109.865 115.402 109.576 114.958 109.576C114.51 109.576 114.184 109.865 114.186 110.253C114.184 110.525 114.341 110.759 114.585 110.821V110.836C114.305 110.887 114.089 111.161 114.092 111.483C114.089 111.898 114.444 112.196 114.958 112.196ZM114.958 111.922C114.608 111.922 114.393 111.742 114.396 111.469C114.393 111.181 114.631 110.976 114.958 110.976C115.281 110.976 115.519 111.181 115.521 111.469C115.519 111.742 115.303 111.922 114.958 111.922ZM114.958 110.712C114.679 110.712 114.483 110.537 114.485 110.273C114.483 110.014 114.672 109.845 114.958 109.845C115.24 109.845 115.429 110.014 115.431 110.273C115.429 110.537 115.232 110.712 114.958 110.712Z"
|
||||
fill="#9BA6B6"
|
||||
/>
|
||||
<rect x="10.2712" y="119.388" width="15.1717" height="15.1717" fill="#DBEAFE" />
|
||||
<path
|
||||
d="M17.1416 125.659H16.7021L16.0684 126.066V126.489L16.6648 126.108H16.6797V128.208H17.1416V125.659ZM18.6742 125.624C18.1214 125.62 17.7541 126 17.7541 126.498C17.7554 126.978 18.0978 127.326 18.5447 127.326C18.8198 127.326 19.0589 127.192 19.1821 126.973H19.1996C19.1983 127.519 19.0004 127.844 18.648 127.844C18.4277 127.844 18.2795 127.717 18.2335 127.514H17.779C17.8313 127.936 18.165 128.243 18.648 128.243C19.2593 128.243 19.649 127.733 19.6478 126.855C19.6465 125.926 19.1635 125.626 18.6742 125.624ZM18.6754 125.997C18.9481 125.997 19.1485 126.224 19.1485 126.484C19.1498 126.748 18.9394 126.975 18.6704 126.975C18.399 126.975 18.2036 126.76 18.2023 126.488C18.2023 126.214 18.4053 125.997 18.6754 125.997Z"
|
||||
fill="#3B82F6"
|
||||
/>
|
||||
<rect x="26.3188" y="119.388" width="15.1717" height="15.1717" fill="#DBEAFE" />
|
||||
<path
|
||||
d="M31.8734 128.208H33.6213V127.822H32.5108V127.805L32.9503 127.358C33.4458 126.882 33.5827 126.651 33.5827 126.363C33.5827 125.936 33.2354 125.624 32.7224 125.624C32.217 125.624 31.8584 125.937 31.8584 126.422H32.2979C32.2979 126.162 32.4622 125.998 32.7162 125.998C32.959 125.998 33.1395 126.147 33.1395 126.387C33.1395 126.6 33.01 126.752 32.7585 127.007L31.8734 127.875V128.208ZM34.9933 128.257C35.6083 128.258 35.9756 127.773 35.9756 126.936C35.9756 126.104 35.6058 125.624 34.9933 125.624C34.3808 125.624 34.0122 126.103 34.011 126.936C34.011 127.771 34.3783 128.257 34.9933 128.257ZM34.9933 127.867C34.6758 127.867 34.4766 127.548 34.4779 126.936C34.4791 126.328 34.6771 126.008 34.9933 126.008C35.3108 126.008 35.5087 126.328 35.51 126.936C35.51 127.548 35.312 127.867 34.9933 127.867Z"
|
||||
fill="#3B82F6"
|
||||
/>
|
||||
<rect x="42.3665" y="119.388" width="15.1717" height="15.1717" fill="#DBEAFE" />
|
||||
<path
|
||||
d="M48.2479 128.208H49.9959V127.822H48.8854V127.805L49.3248 127.358C49.8203 126.882 49.9573 126.651 49.9573 126.363C49.9573 125.936 49.6099 125.624 49.097 125.624C48.5915 125.624 48.233 125.937 48.233 126.422H48.6725C48.6725 126.162 48.8368 125.998 49.0908 125.998C49.3335 125.998 49.5141 126.147 49.5141 126.387C49.5141 126.6 49.3846 126.752 49.1331 127.007L48.2479 127.875V128.208ZM51.4625 125.659H51.023L50.3893 126.066V126.489L50.9856 126.108H51.0006V128.208H51.4625V125.659Z"
|
||||
fill="#3B82F6"
|
||||
/>
|
||||
<rect x="58.4143" y="119.388" width="15.1717" height="15.1717" fill="#DBEAFE" />
|
||||
<path
|
||||
d="M64.049 128.208H65.797V127.822H64.6865V127.805L65.1259 127.358C65.6214 126.882 65.7584 126.651 65.7584 126.363C65.7584 125.936 65.411 125.624 64.8981 125.624C64.3926 125.624 64.0341 125.937 64.0341 126.422H64.4736C64.4736 126.162 64.6379 125.998 64.8919 125.998C65.1347 125.998 65.3152 126.147 65.3152 126.387C65.3152 126.6 65.1857 126.752 64.9342 127.007L64.049 127.875V128.208ZM66.2265 128.208H67.9745V127.822H66.8639V127.805L67.3034 127.358C67.7989 126.882 67.9359 126.651 67.9359 126.363C67.9359 125.936 67.5885 125.624 67.0756 125.624C66.5701 125.624 66.2116 125.937 66.2116 126.422H66.651C66.651 126.162 66.8154 125.998 67.0694 125.998C67.3121 125.998 67.4927 126.147 67.4927 126.387C67.4927 126.6 67.3632 126.752 67.1117 127.007L66.2265 127.875V128.208Z"
|
||||
fill="#3B82F6"
|
||||
/>
|
||||
<rect x="74.4617" y="119.388" width="15.1717" height="15.1717" fill="#DBEAFE" />
|
||||
<path
|
||||
d="M80.0436 128.208H81.7915V127.822H80.681V127.805L81.1205 127.358C81.616 126.882 81.7529 126.651 81.7529 126.363C81.7529 125.936 81.4056 125.624 80.8926 125.624C80.3872 125.624 80.0286 125.937 80.0286 126.422H80.4681C80.4681 126.162 80.6324 125.998 80.8864 125.998C81.1292 125.998 81.3097 126.147 81.3097 126.387C81.3097 126.6 81.1802 126.752 80.9287 127.007L80.0436 127.875V128.208ZM83.1361 128.243C83.6814 128.243 84.0786 127.931 84.0773 127.5C84.0786 127.181 83.8794 126.952 83.5221 126.901V126.881C83.7984 126.821 83.9839 126.616 83.9827 126.33C83.9839 125.941 83.6528 125.624 83.1436 125.624C82.6481 125.624 82.2646 125.919 82.2547 126.346H82.6991C82.7066 126.132 82.9058 125.998 83.1411 125.998C83.3789 125.998 83.537 126.143 83.5357 126.357C83.537 126.58 83.3527 126.728 83.0888 126.728H82.8635V127.084H83.0888C83.4112 127.084 83.603 127.246 83.6017 127.476C83.603 127.702 83.4075 127.856 83.1349 127.856C82.8784 127.856 82.6804 127.723 82.6692 127.515H82.2011C82.2136 127.946 82.5983 128.243 83.1361 128.243Z"
|
||||
fill="#3B82F6"
|
||||
/>
|
||||
<path
|
||||
d="M96.2007 128.208H97.8092V127.934H96.624V127.914L97.1967 127.302C97.6349 126.833 97.7644 126.613 97.7644 126.331C97.7644 125.937 97.4456 125.624 96.9975 125.624C96.5505 125.624 96.2106 125.928 96.2106 126.376H96.5044C96.5044 126.086 96.6924 125.893 96.9875 125.893C97.2639 125.893 97.4755 126.062 97.4755 126.331C97.4755 126.566 97.3373 126.74 97.0423 127.063L96.2007 127.984V128.208ZM98.2088 127.685H99.409V128.208H99.7028V127.685H100.051V127.412H99.7028V125.659H99.3293L98.2088 127.431V127.685ZM99.409 127.412H98.5425V127.392L99.3891 126.052H99.409V127.412Z"
|
||||
fill="#9BA6B6"
|
||||
/>
|
||||
<path
|
||||
d="M112.279 128.208H113.888V127.934H112.702V127.914L113.275 127.302C113.713 126.833 113.843 126.613 113.843 126.331C113.843 125.937 113.524 125.624 113.076 125.624C112.629 125.624 112.289 125.928 112.289 126.376H112.583C112.583 126.086 112.771 125.893 113.066 125.893C113.342 125.893 113.554 126.062 113.554 126.331C113.554 126.566 113.416 126.74 113.121 127.063L112.279 127.984V128.208ZM115.199 128.243C115.684 128.243 116.04 127.885 116.04 127.397C116.04 126.902 115.697 126.54 115.228 126.54C115.057 126.54 114.89 126.601 114.785 126.684H114.77L114.86 125.932H115.926V125.659H114.601L114.447 126.914L114.735 126.948C114.841 126.872 115.022 126.818 115.179 126.819C115.504 126.821 115.741 127.068 115.741 127.402C115.741 127.729 115.512 127.969 115.199 127.969C114.937 127.969 114.729 127.801 114.706 127.571H114.407C114.425 127.959 114.76 128.243 115.199 128.243Z"
|
||||
fill="#9BA6B6"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g clipPath="url(#clip0)">
|
||||
<path
|
||||
d="M54.1289 22.6026C54.1289 16.0129 59.4709 10.671 66.0605 10.671C72.6502 10.671 77.9922 16.0129 77.9922 22.6026C77.9922 29.1923 72.6502 34.5342 66.0605 34.5342C59.4709 34.5342 54.1289 29.1923 54.1289 22.6026Z"
|
||||
fill="#F4F5F6"
|
||||
/>
|
||||
<path
|
||||
d="M77.7885 31.2806C77.9226 31.4509 77.9922 31.6625 77.9922 31.8793V33.5352C77.9922 34.0875 77.5445 34.5352 76.9922 34.5352H55.1289C54.5766 34.5352 54.1289 34.0875 54.1289 33.5352V31.889C54.1289 31.673 54.198 31.4621 54.3312 31.2921C55.6901 29.5582 57.4178 28.1462 59.3905 27.1595C61.4626 26.1232 63.7478 25.5845 66.0645 25.5865C70.8206 25.5865 75.0583 27.8133 77.7885 31.2806ZM70.0397 19.6197C70.0397 20.6745 69.6207 21.6861 68.8748 22.432C68.129 23.1779 67.1173 23.5969 66.0625 23.5969C65.0077 23.5969 63.9961 23.1779 63.2502 22.432C62.5043 21.6861 62.0853 20.6745 62.0853 19.6197C62.0853 18.5648 62.5043 17.5532 63.2502 16.8074C63.9961 16.0615 65.0077 15.6425 66.0625 15.6425C67.1173 15.6425 68.129 16.0615 68.8748 16.8074C69.6207 17.5532 70.0397 18.5648 70.0397 19.6197Z"
|
||||
fill="#708097"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_dd"
|
||||
x="5.49919"
|
||||
y="53.0038"
|
||||
width="121.001"
|
||||
height="88.3916"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB">
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feMorphology
|
||||
radius="0.516129"
|
||||
operator="erode"
|
||||
in="SourceAlpha"
|
||||
result="effect1_dropShadow"
|
||||
/>
|
||||
<feOffset dy="1.03226" />
|
||||
<feGaussianBlur stdDeviation="1.03226" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0" />
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feMorphology
|
||||
radius="0.516129"
|
||||
operator="erode"
|
||||
in="SourceAlpha"
|
||||
result="effect2_dropShadow"
|
||||
/>
|
||||
<feOffset dy="2.06452" />
|
||||
<feGaussianBlur stdDeviation="1.54839" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" />
|
||||
<feBlend mode="normal" in2="effect1_dropShadow" result="effect2_dropShadow" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow" result="shape" />
|
||||
</filter>
|
||||
<clipPath id="clip0">
|
||||
<path
|
||||
d="M54.1289 22.6026C54.1289 16.0129 59.4709 10.671 66.0605 10.671C72.6502 10.671 77.9922 16.0129 77.9922 22.6026C77.9922 29.1923 72.6502 34.5342 66.0605 34.5342C59.4709 34.5342 54.1289 29.1923 54.1289 22.6026Z"
|
||||
fill="white"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<h3 className="mt-2 text-xl font-bold text-neutral-900">Create your first event type</h3>
|
||||
<p className="mt-1 text-md text-neutral-600">
|
||||
Event types enable you to share links that show available times on your calendar and allow
|
||||
people to make bookings with you.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{showAddModal && (
|
||||
<div
|
||||
className="fixed z-10 inset-0 overflow-y-auto"
|
||||
aria-labelledby="modal-title"
|
||||
role="dialog"
|
||||
aria-modal="true">
|
||||
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
||||
<div
|
||||
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
|
||||
aria-hidden="true"></div>
|
||||
|
||||
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
|
||||
​
|
||||
</span>
|
||||
|
||||
<div className="inline-block align-bottom bg-white rounded-sm px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6">
|
||||
<div className="mb-4">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900" id="modal-title">
|
||||
Add a new event type
|
||||
</h3>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">
|
||||
Create a new event type for people to book times with.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<form onSubmit={createEventTypeHandler}>
|
||||
<div>
|
||||
<div className="mb-4">
|
||||
<label htmlFor="title" className="block text-sm font-medium text-gray-700">
|
||||
Title
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<input
|
||||
ref={titleRef}
|
||||
type="text"
|
||||
name="title"
|
||||
id="title"
|
||||
required
|
||||
className="shadow-sm focus:ring-neutral-900 focus:border-neutral-900 block w-full sm:text-sm border-gray-300 rounded-sm"
|
||||
placeholder="Quick Chat"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label htmlFor="slug" className="block text-sm font-medium text-gray-700">
|
||||
URL
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<div className="flex rounded-sm shadow-sm">
|
||||
<span className="inline-flex items-center px-3 rounded-l-md border border-r-0 border-gray-300 bg-gray-50 text-gray-500 sm:text-sm">
|
||||
{location.hostname}/{user.username}/
|
||||
</span>
|
||||
<input
|
||||
ref={slugRef}
|
||||
type="text"
|
||||
name="slug"
|
||||
id="slug"
|
||||
required
|
||||
className="flex-1 block w-full focus:ring-neutral-900 focus:border-neutral-900 min-w-0 rounded-none rounded-r-md sm:text-sm border-gray-300"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label htmlFor="description" className="block text-sm font-medium text-gray-700">
|
||||
Description
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<textarea
|
||||
ref={descriptionRef}
|
||||
name="description"
|
||||
id="description"
|
||||
className="shadow-sm focus:ring-neutral-900 focus:border-neutral-900 block w-full sm:text-sm border-gray-300 rounded-sm"
|
||||
placeholder="A quick video meeting."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label htmlFor="length" className="block text-sm font-medium text-gray-700">
|
||||
Length
|
||||
</label>
|
||||
<div className="mt-1 relative rounded-sm shadow-sm">
|
||||
<input
|
||||
ref={lengthRef}
|
||||
type="number"
|
||||
name="length"
|
||||
id="length"
|
||||
required
|
||||
className="focus:ring-neutral-900 focus:border-neutral-900 block w-full pr-20 sm:text-sm border-gray-300 rounded-sm"
|
||||
placeholder="15"
|
||||
/>
|
||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 text-sm">
|
||||
minutes
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* TODO: Add an error message when required input fields empty*/}
|
||||
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<button type="submit" className="btn btn-primary">
|
||||
Create
|
||||
</button>
|
||||
<button onClick={toggleAddModal} type="button" className="btn btn-white mr-2">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Shell>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
const session = await getSession(context);
|
||||
if (!session) {
|
||||
return { redirect: { permanent: false, destination: "/auth/login" } };
|
||||
}
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: session.user.email,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
bufferTime: true,
|
||||
},
|
||||
});
|
||||
|
||||
const types = await prisma.eventType.findMany({
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
slug: true,
|
||||
description: true,
|
||||
length: true,
|
||||
hidden: true,
|
||||
},
|
||||
});
|
||||
return {
|
||||
props: { user, types }, // will be passed to the page component as props
|
||||
};
|
||||
}
|
377
pages/index.tsx
|
@ -1,368 +1,23 @@
|
|||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import prisma from "../lib/prisma";
|
||||
import Shell from "../components/Shell";
|
||||
import { getSession, useSession } from "next-auth/client";
|
||||
import { CheckIcon, ClockIcon, InformationCircleIcon } from "@heroicons/react/outline";
|
||||
import DonateBanner from "../components/DonateBanner";
|
||||
import Loader from "@components/Loader";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
function classNames(...classes) {
|
||||
return classes.filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
export default function Home(props) {
|
||||
const [session, loading] = useSession();
|
||||
if (loading) {
|
||||
return <div className="loader"></div>;
|
||||
function RedirectPage() {
|
||||
const router = useRouter();
|
||||
if (typeof window !== "undefined") {
|
||||
router.push("/event-types");
|
||||
return;
|
||||
}
|
||||
|
||||
function convertMinsToHrsMins(mins) {
|
||||
let h = Math.floor(mins / 60);
|
||||
let m = mins % 60;
|
||||
h = h < 10 ? "0" + h : h;
|
||||
m = m < 10 ? "0" + m : m;
|
||||
return `${h}:${m}`;
|
||||
}
|
||||
|
||||
const stats = [
|
||||
{ name: "Event Types", stat: props.eventTypeCount },
|
||||
{ name: "Integrations", stat: props.integrationCount },
|
||||
{
|
||||
name: "Available Hours",
|
||||
stat: Math.round(((props.user.endTime - props.user.startTime) / 60) * 100) / 100 + " hours",
|
||||
},
|
||||
];
|
||||
|
||||
let timeline = [];
|
||||
|
||||
if (session) {
|
||||
timeline = [
|
||||
{
|
||||
id: 1,
|
||||
content: "Add your first",
|
||||
target: "integration",
|
||||
href: "/integrations",
|
||||
icon: props.integrationCount != 0 ? CheckIcon : InformationCircleIcon,
|
||||
iconBackground: props.integrationCount != 0 ? "bg-green-400" : "bg-gray-400",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
content: "Add one or more",
|
||||
target: "event types",
|
||||
href: "/availability",
|
||||
icon: props.eventTypeCount != 0 ? CheckIcon : InformationCircleIcon,
|
||||
iconBackground: props.eventTypeCount != 0 ? "bg-green-400" : "bg-gray-400",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
content: "Complete your",
|
||||
target: "profile",
|
||||
href: "/settings/profile",
|
||||
icon: session.user.image ? CheckIcon : InformationCircleIcon,
|
||||
iconBackground: session.user.image ? "bg-green-400" : "bg-gray-400",
|
||||
},
|
||||
];
|
||||
} else {
|
||||
timeline = [];
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Head>
|
||||
<title>Calendso</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
|
||||
<Shell heading="Dashboard">
|
||||
<div className="md:grid grid-cols-3 gap-4">
|
||||
<div className="col-span-2">
|
||||
<div className="rounded-lg bg-white shadow dark:bg-gray-800">
|
||||
<div className="pt-5 pb-2 px-6 sm:flex sm:items-center sm:justify-between">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">Your stats</h3>
|
||||
</div>
|
||||
<dl className="grid grid-cols-1 overflow-hidden divide-y divide-gray-200 dark:divide-gray-900 md:grid-cols-3 md:divide-y-0 md:divide-x">
|
||||
{stats.map((item) => (
|
||||
<div key={item.name} className="px-4 py-5 sm:p-6">
|
||||
<dt className="text-base font-normal dark:text-white text-gray-900">{item.name}</dt>
|
||||
<dd className="mt-1 flex justify-between items-baseline md:block lg:flex">
|
||||
<div className="flex items-baseline text-2xl font-semibold text-blue-600">
|
||||
{item.stat}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</div>
|
||||
<div className="mt-8 bg-white shadow dark:bg-gray-800 overflow-hidden rounded-md">
|
||||
<div className="pt-5 pb-2 px-6 sm:flex sm:items-center sm:justify-between">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">
|
||||
Your event types
|
||||
</h3>
|
||||
</div>
|
||||
<ul className="divide-y divide-gray-200">
|
||||
{props.eventTypes.map((type) => (
|
||||
<li key={type.id} className="flex">
|
||||
<div className="px-4 py-4 flex items-center sm:px-6 w-2/3">
|
||||
<div className="min-w-0 flex-1 sm:flex sm:items-center sm:justify-between">
|
||||
<div className="truncate">
|
||||
<div className="flex text-sm">
|
||||
<p className="flex-shrink-0 font-medium text-blue-600 truncate">{type.title}</p>
|
||||
<p className="ml-1 font-normal text-gray-500 truncate">in {type.description}</p>
|
||||
</div>
|
||||
<div className="mt-2 flex">
|
||||
<div className="flex items-center text-sm text-gray-500">
|
||||
<ClockIcon
|
||||
className="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<p>{type.length} minutes</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-5 w-1/3 text-right pt-5 pr-4">
|
||||
<Link href={"/availability/event/" + type.id}>
|
||||
<a className="text-gray-400 hover:text-gray-900 mr-4 font-medium">Edit</a>
|
||||
</Link>
|
||||
<Link href={"/" + session.user.username + "/" + type.slug}>
|
||||
<a target="_blank" className="text-blue-600 hover:text-blue-900 mr-2 font-medium">
|
||||
View
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
{props.eventTypes.length == 0 && (
|
||||
<div className="text-center text-gray-400 py-12">
|
||||
<p>You haven't created any event types.</p>
|
||||
</div>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="mt-8 bg-white dark:bg-gray-800 shadow overflow-hidden rounded-md p-6 mb-8 md:mb-0">
|
||||
<div className="md:flex">
|
||||
<div className="md:w-1/2 self-center mb-8 md:mb-0">
|
||||
<h2 className="text-2xl dark:text-white font-semibold">Getting started</h2>
|
||||
<p className="text-gray-600 dark:text-gray-200 text-sm">
|
||||
Steps you should take to get started with Calendso.
|
||||
</p>
|
||||
</div>
|
||||
<div className="md:w-1/2">
|
||||
<div className="flow-root">
|
||||
<ul className="-mb-8">
|
||||
{timeline.map((event, eventIdx) => (
|
||||
<li key={event.id}>
|
||||
<div className="relative pb-8">
|
||||
{eventIdx !== timeline.length - 1 ? (
|
||||
<span
|
||||
className="absolute top-4 left-4 -ml-px h-full w-0.5 bg-gray-200 dark:bg-gray-900"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : null}
|
||||
<div className="relative flex space-x-3">
|
||||
<div>
|
||||
<span
|
||||
className={classNames(
|
||||
event.iconBackground,
|
||||
"h-8 w-8 rounded-full flex items-center justify-center ring-8 ring-white dark:ring-gray-800"
|
||||
)}>
|
||||
<event.icon className="h-5 w-5 text-white" aria-hidden="true" />
|
||||
</span>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 pt-1.5 flex justify-between space-x-4">
|
||||
<div>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-200">
|
||||
{event.content}{" "}
|
||||
<Link href={event.href}>
|
||||
<a className="font-medium dark:text-white text-gray-900">
|
||||
{event.target}
|
||||
</a>
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow px-5 py-6 md:py-7 sm:px-6">
|
||||
<div className="mb-4 sm:flex sm:items-center sm:justify-between">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">Your day</h3>
|
||||
<div className="mt-3 sm:mt-0 sm:ml-4">
|
||||
<Link href="/availability">
|
||||
<a className="text-sm text-gray-400">Configure</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-2xl font-semibold text-gray-600 dark:text-white">
|
||||
Offering time slots between{" "}
|
||||
<span className="text-blue-600">{convertMinsToHrsMins(props.user.startTime)}</span> and{" "}
|
||||
<span className="text-blue-600">{convertMinsToHrsMins(props.user.endTime)}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 bg-white dark:bg-gray-800 rounded-lg shadow px-5 py-6 md:py-7 sm:px-6">
|
||||
<div className="mb-8 sm:flex sm:items-center sm:justify-between">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">
|
||||
Your integrations
|
||||
</h3>
|
||||
<div className="mt-3 sm:mt-0 sm:ml-4">
|
||||
<Link href="/integrations">
|
||||
<a className="text-sm text-gray-400">View more</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<ul className="divide-y divide-gray-200">
|
||||
{props.credentials.map((integration) => (
|
||||
<li key={integration.type} className="pb-4 flex">
|
||||
{integration.type == "google_calendar" && (
|
||||
<img
|
||||
className="h-10 w-10 mr-2"
|
||||
src="integrations/google-calendar.png"
|
||||
alt="Google Calendar"
|
||||
/>
|
||||
)}
|
||||
{integration.type == "office365_calendar" && (
|
||||
<img
|
||||
className="h-10 w-10 mr-2"
|
||||
src="integrations/office-365.png"
|
||||
alt="Office 365 / Outlook.com Calendar"
|
||||
/>
|
||||
)}
|
||||
{integration.type == "zoom_video" && (
|
||||
<img className="h-10 w-10 mr-2" src="integrations/zoom.png" alt="Zoom" />
|
||||
)}
|
||||
<div className="ml-3">
|
||||
{integration.type == "office365_calendar" && (
|
||||
<p className="text-sm font-medium text-gray-900">Office 365 / Outlook.com Calendar</p>
|
||||
)}
|
||||
{integration.type == "google_calendar" && (
|
||||
<p className="text-sm font-medium text-gray-900">Google Calendar</p>
|
||||
)}
|
||||
{integration.type == "zoom_video" && (
|
||||
<p className="text-sm font-medium text-gray-900">Zoom</p>
|
||||
)}
|
||||
{integration.type.endsWith("_calendar") && (
|
||||
<p className="text-sm text-gray-500">Calendar Integration</p>
|
||||
)}
|
||||
{integration.type.endsWith("_video") && (
|
||||
<p className="text-sm text-gray-500">Video Conferencing</p>
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
{props.credentials.length == 0 && (
|
||||
<div className="text-center text-gray-400 py-2">
|
||||
<p>You haven't added any integrations.</p>
|
||||
</div>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="mt-8 bg-white dark:bg-gray-800 rounded-lg shadow px-5 py-6 md:py-7 sm:px-6">
|
||||
<div className="mb-4 sm:flex sm:items-center sm:justify-between">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">
|
||||
Your event types
|
||||
</h3>
|
||||
<div className="mt-3 sm:mt-0 sm:ml-4">
|
||||
<Link href="/availability">
|
||||
<a className="text-sm text-gray-400">View more</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<ul className="divide-y divide-gray-200">
|
||||
{props.eventTypes.map((type) => (
|
||||
<li
|
||||
key={type.id}
|
||||
className="relative py-5 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600">
|
||||
<div className="flex justify-between space-x-3">
|
||||
<div className="min-w-0 flex-1">
|
||||
<a href="#" className="block focus:outline-none">
|
||||
<span className="absolute inset-0" aria-hidden="true" />
|
||||
<p className="text-sm font-medium text-gray-900 dark:text-white truncate">
|
||||
{type.title}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500 truncate">{type.description}</p>
|
||||
</a>
|
||||
</div>
|
||||
<span className="flex-shrink-0 whitespace-nowrap text-sm text-gray-500">
|
||||
{type.length} minutes
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
{props.eventTypes.length == 0 && (
|
||||
<div className="text-center text-gray-400 py-2">
|
||||
<p>You haven't created any event types.</p>
|
||||
</div>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DonateBanner />
|
||||
</Shell>
|
||||
</div>
|
||||
<Loader/>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
const session = await getSession(context);
|
||||
|
||||
let user = [];
|
||||
let credentials = [];
|
||||
let eventTypes = [];
|
||||
|
||||
if (session) {
|
||||
user = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: session.user.email,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
},
|
||||
});
|
||||
|
||||
credentials = await prisma.credential.findMany({
|
||||
where: {
|
||||
userId: session.user.id,
|
||||
},
|
||||
select: {
|
||||
type: true,
|
||||
},
|
||||
});
|
||||
|
||||
eventTypes = (
|
||||
await prisma.eventType.findMany({
|
||||
where: {
|
||||
userId: session.user.id,
|
||||
},
|
||||
})
|
||||
).map((eventType) => {
|
||||
return {
|
||||
...eventType,
|
||||
periodStartDate: eventType.periodStartDate?.toString() ?? null,
|
||||
periodEndDate: eventType.periodEndDate?.toString() ?? null,
|
||||
};
|
||||
});
|
||||
RedirectPage.getInitialProps = (ctx) => {
|
||||
if (ctx.res) {
|
||||
ctx.res.writeHead(302, { Location: "/event-types" });
|
||||
ctx.res.end();
|
||||
}
|
||||
return {
|
||||
props: {
|
||||
user,
|
||||
credentials,
|
||||
eventTypes,
|
||||
eventTypeCount: eventTypes.length,
|
||||
integrationCount: credentials.length,
|
||||
}, // will be passed to the page component as props
|
||||
};
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
export default RedirectPage;
|
||||
|
|
|
@ -1,130 +1,129 @@
|
|||
import Head from 'next/head';
|
||||
import prisma from '../../lib/prisma';
|
||||
import { getIntegrationName, getIntegrationType } from '../../lib/integrations';
|
||||
import Shell from '../../components/Shell';
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useSession, getSession } from 'next-auth/client';
|
||||
import Head from "next/head";
|
||||
import prisma from "../../lib/prisma";
|
||||
import { getIntegrationName, getIntegrationType } from "../../lib/integrations";
|
||||
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';
|
||||
|
||||
export default function integration(props) {
|
||||
const router = useRouter();
|
||||
const [session, loading] = useSession();
|
||||
const [showAPIKey, setShowAPIKey] = useState(false);
|
||||
const router = useRouter();
|
||||
const [session, loading] = useSession();
|
||||
const [showAPIKey, setShowAPIKey] = useState(false);
|
||||
|
||||
if (loading) {
|
||||
return <div className="loader"></div>;
|
||||
}
|
||||
if (loading) {
|
||||
return <Loader/>;
|
||||
}
|
||||
|
||||
function toggleShowAPIKey() {
|
||||
setShowAPIKey(!showAPIKey);
|
||||
}
|
||||
function toggleShowAPIKey() {
|
||||
setShowAPIKey(!showAPIKey);
|
||||
}
|
||||
|
||||
async function deleteIntegrationHandler(event) {
|
||||
event.preventDefault();
|
||||
async function deleteIntegrationHandler(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const response = await fetch('/api/integrations', {
|
||||
method: 'DELETE',
|
||||
body: JSON.stringify({id: props.integration.id}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
const response = await fetch("/api/integrations", {
|
||||
method: "DELETE",
|
||||
body: JSON.stringify({ id: props.integration.id }),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
router.push('/integrations');
|
||||
}
|
||||
router.push("/integrations");
|
||||
}
|
||||
|
||||
return(
|
||||
<div>
|
||||
<Head>
|
||||
<title>{getIntegrationName(props.integration.type)} | Integrations | Calendso</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
return (
|
||||
<div>
|
||||
<Head>
|
||||
<title>{getIntegrationName(props.integration.type)} | Integrations | Calendso</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
|
||||
<Shell heading={getIntegrationName(props.integration.type)}>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="col-span-2 bg-white shadow overflow-hidden rounded-lg">
|
||||
<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.
|
||||
</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>
|
||||
<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>
|
||||
<dd className="mt-1 text-sm text-gray-900">
|
||||
{getIntegrationType(props.integration.type)}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">
|
||||
API Key
|
||||
</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">
|
||||
{!showAPIKey ?
|
||||
<span>••••••••</span>
|
||||
:
|
||||
<div>
|
||||
<textarea name="apikey" rows={6} className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" readOnly>{JSON.stringify(props.integration.key)}</textarea>
|
||||
</div>}
|
||||
<button onClick={toggleShowAPIKey} className="ml-2 font-medium text-blue-600 hover:text-blue-700">{!showAPIKey ? 'Show' : 'Hide'}</button>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="bg-white shadow rounded-lg">
|
||||
<div className="px-4 py-5 sm:p-6">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">
|
||||
Delete this integration
|
||||
</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>
|
||||
</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-md text-red-700 bg-red-100 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:text-sm">
|
||||
Delete integration
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<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.
|
||||
</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>
|
||||
<dd className="mt-1 text-sm text-gray-900">{getIntegrationName(props.integration.type)}</dd>
|
||||
</div>
|
||||
</Shell>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">Integration type</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">{getIntegrationType(props.integration.type)}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">API Key</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">
|
||||
{!showAPIKey ? (
|
||||
<span>••••••••</span>
|
||||
) : (
|
||||
<div>
|
||||
<textarea
|
||||
name="apikey"
|
||||
rows={6}
|
||||
className="shadow-sm focus:ring-neutral-500 focus:border-neutral-500 block w-full sm:text-sm border-gray-300 rounded-sm"
|
||||
readOnly>
|
||||
{JSON.stringify(props.integration.key)}
|
||||
</textarea>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
onClick={toggleShowAPIKey}
|
||||
className="ml-2 font-medium text-neutral-900 hover:text-neutral-700">
|
||||
{!showAPIKey ? "Show" : "Hide"}
|
||||
</button>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="bg-white shadow 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>
|
||||
<div className="mt-2 max-w-xl text-sm text-gray-500">
|
||||
<p>Once you delete this integration, 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
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
</Shell>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
const session = await getSession(context);
|
||||
const session = await getSession(context);
|
||||
|
||||
const integration = await prisma.credential.findFirst({
|
||||
where: {
|
||||
id: parseInt(context.query.integration),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
type: true,
|
||||
key: true
|
||||
}
|
||||
});
|
||||
return {
|
||||
props: {integration}, // will be passed to the page component as props
|
||||
}
|
||||
const integration = await prisma.credential.findFirst({
|
||||
where: {
|
||||
id: parseInt(context.query.integration),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
type: true,
|
||||
key: true,
|
||||
},
|
||||
});
|
||||
return {
|
||||
props: { integration }, // will be passed to the page component as props
|
||||
};
|
||||
}
|
|
@ -1,387 +1,464 @@
|
|||
import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
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 {InformationCircleIcon} from '@heroicons/react/outline';
|
||||
import {Switch} from '@headlessui/react'
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
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 { InformationCircleIcon } from "@heroicons/react/outline";
|
||||
import { Switch } from "@headlessui/react";
|
||||
import Loader from '@components/Loader';
|
||||
|
||||
export default function Home({ integrations }) {
|
||||
const [session, loading] = useSession();
|
||||
const [showAddModal, setShowAddModal] = useState(false);
|
||||
const [showSelectCalendarModal, setShowSelectCalendarModal] = useState(false);
|
||||
const [selectableCalendars, setSelectableCalendars] = useState([]);
|
||||
const [session, loading] = useSession();
|
||||
|
||||
function toggleAddModal() {
|
||||
setShowAddModal(!showAddModal);
|
||||
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())
|
||||
.then((data) => {
|
||||
setSelectableCalendars(data);
|
||||
});
|
||||
}
|
||||
|
||||
function integrationHandler(type) {
|
||||
fetch("/api/integrations/" + type.replace("_", "") + "/add")
|
||||
.then((response) => response.json())
|
||||
.then((data) => (window.location.href = data.url));
|
||||
}
|
||||
|
||||
function calendarSelectionHandler(calendar) {
|
||||
return (selected) => {
|
||||
const cals = [...selectableCalendars];
|
||||
const i = cals.findIndex((c) => c.externalId === calendar.externalId);
|
||||
cals[i].selected = selected;
|
||||
setSelectableCalendars(cals);
|
||||
if (selected) {
|
||||
fetch("api/availability/calendar", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(cals[i]),
|
||||
}).then((response) => response.json());
|
||||
} else {
|
||||
fetch("api/availability/calendar", {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(cals[i]),
|
||||
}).then((response) => response.json());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getCalendarIntegrationImage(integrationType: string) {
|
||||
switch (integrationType) {
|
||||
case "google_calendar":
|
||||
return "integrations/google-calendar.svg";
|
||||
case "office365_calendar":
|
||||
return "integrations/outlook.svg";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function toggleShowCalendarModal() {
|
||||
setShowSelectCalendarModal(!showSelectCalendarModal);
|
||||
}
|
||||
function classNames(...classes) {
|
||||
return classes.filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
function loadCalendars() {
|
||||
fetch('api/availability/calendar')
|
||||
.then((response) => response.json())
|
||||
.then(data => {
|
||||
setSelectableCalendars(data)
|
||||
});
|
||||
}
|
||||
|
||||
function integrationHandler(type) {
|
||||
fetch('/api/integrations/' + type.replace('_', '') + '/add')
|
||||
.then((response) => response.json())
|
||||
.then((data) => window.location.href = data.url);
|
||||
}
|
||||
|
||||
function calendarSelectionHandler(calendar) {
|
||||
return (selected) => {
|
||||
let cals = [...selectableCalendars];
|
||||
let i = cals.findIndex(c => c.externalId === calendar.externalId);
|
||||
cals[i].selected = selected;
|
||||
setSelectableCalendars(cals);
|
||||
if (selected) {
|
||||
fetch('api/availability/calendar', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(cals[i])
|
||||
}).then((response) => response.json());
|
||||
} else {
|
||||
fetch('api/availability/calendar', {
|
||||
method: 'DELETE', headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}, body: JSON.stringify(cals[i])
|
||||
}).then((response) => response.json());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getCalendarIntegrationImage(integrationType: string){
|
||||
switch (integrationType) {
|
||||
case "google_calendar": return "integrations/google-calendar.png";
|
||||
case "office365_calendar": return "integrations/office-365.png";
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
function classNames(...classes) {
|
||||
return classes.filter(Boolean).join(' ')
|
||||
}
|
||||
|
||||
useEffect(loadCalendars, [integrations]);
|
||||
|
||||
if (loading) {
|
||||
return <div className="loader"></div>;
|
||||
}
|
||||
useEffect(loadCalendars, [integrations]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div>
|
||||
<Head>
|
||||
<title>Integrations | Calendso</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
|
||||
<Shell heading="Integrations" noPaddingBottom>
|
||||
<div className="text-right py-2">
|
||||
<button onClick={toggleAddModal} type="button"
|
||||
className="px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
Add new integration
|
||||
</button>
|
||||
</div>
|
||||
<div className="bg-white shadow overflow-hidden rounded-lg mb-8">
|
||||
{integrations.filter( (ig) => ig.credential ).length !== 0 ? <ul className="divide-y divide-gray-200">
|
||||
{integrations.filter(ig => ig.credential).map( (ig) => (<li key={ig.id}>
|
||||
<Link href={"/integrations/" + ig.credential.id}>
|
||||
<a className="block hover:bg-gray-50">
|
||||
<div className="flex items-center px-4 py-4 sm:px-6">
|
||||
<div className="min-w-0 flex-1 flex items-center">
|
||||
<div className="flex-shrink-0">
|
||||
<img className="h-10 w-10 mr-2" src={ig.imageSrc} alt={ig.title} />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 px-4 md:grid md:grid-cols-2 md:gap-4">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-blue-600 truncate">{ig.title}</p>
|
||||
<p className="flex items-center text-sm text-gray-500">
|
||||
{ig.type.endsWith('_calendar') && <span className="truncate">Calendar Integration</span>}
|
||||
{ig.type.endsWith('_video') && <span className="truncate">Video Conferencing</span>}
|
||||
</p>
|
||||
</div>
|
||||
<div className="hidden md:block">
|
||||
{ig.credential.key && <p className="mt-2 flex items-center text text-gray-500">
|
||||
<CheckCircleIcon className="flex-shrink-0 mr-1.5 h-5 w-5 text-green-400" />
|
||||
Connected
|
||||
</p>}
|
||||
{!ig.credential.key && <p className="mt-3 flex items-center text text-gray-500">
|
||||
<XCircleIcon className="flex-shrink-0 mr-1.5 h-5 w-5 text-yellow-400" />
|
||||
Not connected
|
||||
</p>}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<ChevronRightIcon className="h-5 w-5 text-gray-400" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
</li>))}
|
||||
</ul>
|
||||
:
|
||||
<div className="bg-white shadow rounded-lg">
|
||||
<div className="flex">
|
||||
<div className="py-9 pl-8">
|
||||
<InformationCircleIcon className="text-blue-600 w-16" />
|
||||
</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.
|
||||
</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.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-3 text-sm">
|
||||
<button onClick={toggleAddModal} className="font-medium text-blue-600 hover:text-blue-500"> Add your first integration <span aria-hidden="true">→</span></button>
|
||||
</div>
|
||||
</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-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-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-blue-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<PlusIcon className="h-6 w-6 text-blue-600" />
|
||||
</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-blue-600 hover:text-blue-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-md 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-indigo-500 sm:mt-0 sm:w-auto sm:text-sm">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div className="bg-white shadow rounded-lg">
|
||||
<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>
|
||||
<div className="mt-5">
|
||||
<button type="button" onClick={toggleShowCalendarModal} className="btn btn-primary">
|
||||
Select calendars
|
||||
</button>
|
||||
</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-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-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-blue-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<CalendarIcon className="h-6 w-6 text-blue-600" />
|
||||
</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-indigo-600' : '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-indigo-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-md 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-indigo-500 sm:mt-0 sm:w-auto sm:text-sm">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</Shell>
|
||||
</div>
|
||||
<Loader/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Head>
|
||||
<title>Integrations | 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">
|
||||
{integrations.filter((ig) => ig.credential).length !== 0 ? (
|
||||
<ul className="divide-y divide-gray-200">
|
||||
{integrations
|
||||
.filter((ig) => ig.credential)
|
||||
.map((ig) => (
|
||||
<li key={ig.id}>
|
||||
<Link href={"/integrations/" + ig.credential.id}>
|
||||
<a className="block hover:bg-gray-50">
|
||||
<div className="flex items-center px-4 py-4 sm:px-6">
|
||||
<div className="min-w-0 flex-1 flex items-center">
|
||||
<div className="flex-shrink-0">
|
||||
<img className="h-10 w-10 mr-2" src={ig.imageSrc} alt={ig.title} />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 px-4 md:grid md:grid-cols-2 md:gap-4">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-neutral-900 truncate">{ig.title}</p>
|
||||
<p className="flex items-center text-sm text-gray-500">
|
||||
{ig.type.endsWith("_calendar") && (
|
||||
<span className="truncate">Calendar Integration</span>
|
||||
)}
|
||||
{ig.type.endsWith("_video") && (
|
||||
<span className="truncate">Video Conferencing</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="hidden md:block">
|
||||
{ig.credential.key && (
|
||||
<p className="mt-2 flex items-center text text-gray-500">
|
||||
<CheckCircleIcon className="flex-shrink-0 mr-1.5 h-5 w-5 text-green-400" />
|
||||
Connected
|
||||
</p>
|
||||
)}
|
||||
{!ig.credential.key && (
|
||||
<p className="mt-3 flex items-center text text-gray-500">
|
||||
<XCircleIcon className="flex-shrink-0 mr-1.5 h-5 w-5 text-yellow-400" />
|
||||
Not connected
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<ChevronRightIcon className="h-5 w-5 text-gray-400" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<div className="bg-white shadow rounded-sm">
|
||||
<div className="flex">
|
||||
<div className="py-9 pl-8">
|
||||
<InformationCircleIcon className="text-neutral-900 w-16" />
|
||||
</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.
|
||||
</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.
|
||||
</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>
|
||||
</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="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>
|
||||
<div className="mt-5">
|
||||
<button type="button" onClick={toggleShowCalendarModal} className="btn btn-primary">
|
||||
Select calendars
|
||||
</button>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
const validJson = (jsonString: string) => {
|
||||
try {
|
||||
const o = JSON.parse(jsonString);
|
||||
if (o && typeof o === "object") {
|
||||
return o;
|
||||
}
|
||||
try {
|
||||
const o = JSON.parse(jsonString);
|
||||
if (o && typeof o === "object") {
|
||||
return o;
|
||||
}
|
||||
catch (e) { console.error(e); }
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
const session = await getSession(context);
|
||||
if (!session) {
|
||||
return { redirect: { permanent: false, destination: '/auth/login' } };
|
||||
}
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: session.user.email,
|
||||
},
|
||||
select: {
|
||||
id: true
|
||||
}
|
||||
});
|
||||
const session = await getSession(context);
|
||||
if (!session) {
|
||||
return { redirect: { permanent: false, destination: "/auth/login" } };
|
||||
}
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: session.user.email,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
const credentials = await prisma.credential.findMany({
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
type: true,
|
||||
key: true
|
||||
}
|
||||
});
|
||||
const credentials = await prisma.credential.findMany({
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
type: true,
|
||||
key: true,
|
||||
},
|
||||
});
|
||||
|
||||
const integrations = [ {
|
||||
installed: !!(process.env.GOOGLE_API_CREDENTIALS && validJson(process.env.GOOGLE_API_CREDENTIALS)),
|
||||
credential: credentials.find( (integration) => integration.type === "google_calendar" ) || null,
|
||||
type: "google_calendar",
|
||||
title: "Google Calendar",
|
||||
imageSrc: "integrations/google-calendar.png",
|
||||
description: "For personal and business calendars",
|
||||
}, {
|
||||
installed: !!(process.env.MS_GRAPH_CLIENT_ID && process.env.MS_GRAPH_CLIENT_SECRET),
|
||||
type: "office365_calendar",
|
||||
credential: credentials.find( (integration) => integration.type === "office365_calendar" ) || null,
|
||||
title: "Office 365 / Outlook.com Calendar",
|
||||
imageSrc: "integrations/office-365.png",
|
||||
description: "For personal and business calendars",
|
||||
}, {
|
||||
installed: !!(process.env.ZOOM_CLIENT_ID && process.env.ZOOM_CLIENT_SECRET),
|
||||
type: "zoom_video",
|
||||
credential: credentials.find( (integration) => integration.type === "zoom_video" ) || null,
|
||||
title: "Zoom",
|
||||
imageSrc: "integrations/zoom.png",
|
||||
description: "Video Conferencing",
|
||||
} ];
|
||||
const integrations = [
|
||||
{
|
||||
installed: !!(process.env.GOOGLE_API_CREDENTIALS && validJson(process.env.GOOGLE_API_CREDENTIALS)),
|
||||
credential: credentials.find((integration) => integration.type === "google_calendar") || null,
|
||||
type: "google_calendar",
|
||||
title: "Google Calendar",
|
||||
imageSrc: "integrations/google-calendar.svg",
|
||||
description: "For personal and business calendars",
|
||||
},
|
||||
{
|
||||
installed: !!(process.env.MS_GRAPH_CLIENT_ID && process.env.MS_GRAPH_CLIENT_SECRET),
|
||||
type: "office365_calendar",
|
||||
credential: credentials.find((integration) => integration.type === "office365_calendar") || null,
|
||||
title: "Office 365 / Outlook.com Calendar",
|
||||
imageSrc: "integrations/outlook.svg",
|
||||
description: "For personal and business calendars",
|
||||
},
|
||||
{
|
||||
installed: !!(process.env.ZOOM_CLIENT_ID && process.env.ZOOM_CLIENT_SECRET),
|
||||
type: "zoom_video",
|
||||
credential: credentials.find((integration) => integration.type === "zoom_video") || null,
|
||||
title: "Zoom",
|
||||
imageSrc: "integrations/zoom.svg",
|
||||
description: "Video Conferencing",
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
props: {integrations},
|
||||
}
|
||||
return {
|
||||
props: { integrations },
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,35 +1,21 @@
|
|||
import Head from 'next/head';
|
||||
import Shell from '../../components/Shell';
|
||||
import SettingsShell from '../../components/Settings';
|
||||
import prisma from '../../lib/prisma';
|
||||
import {getSession, useSession} from 'next-auth/client';
|
||||
|
||||
export default function Billing(props) {
|
||||
const [ session, loading ] = useSession();
|
||||
|
||||
if (loading) {
|
||||
return <div className="loader"></div>;
|
||||
}
|
||||
import Head from "next/head";
|
||||
import Shell from "../../components/Shell";
|
||||
import SettingsShell from "../../components/Settings";
|
||||
import prisma from "../../lib/prisma";
|
||||
import { getSession } from "next-auth/client";
|
||||
|
||||
export default function Billing() {
|
||||
return (
|
||||
<Shell heading="Billing">
|
||||
<Shell heading="Billing" subtitle="Manage your billing information and cancel your subscription.">
|
||||
<Head>
|
||||
<title>Billing | Calendso</title>
|
||||
</Head>
|
||||
<SettingsShell>
|
||||
<div className="py-6 px-4 sm:p-6 lg:pb-8 lg:col-span-9">
|
||||
<div className="mb-6">
|
||||
<h2 className="text-lg leading-6 font-medium text-gray-900">
|
||||
Change your Subscription
|
||||
</h2>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
Cancel, update credit card or change plan
|
||||
</p>
|
||||
</div>
|
||||
<div className="py-6 lg:pb-8 lg:col-span-9">
|
||||
<div className="my-6">
|
||||
<iframe
|
||||
src="https://calendso.com/subscription-embed"
|
||||
style={{minHeight: 800, width: "100%", border: 0 }}
|
||||
style={{ minHeight: 800, width: "100%", border: 0 }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -39,28 +25,28 @@ export default function Billing(props) {
|
|||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
const session = await getSession(context);
|
||||
if (!session) {
|
||||
return { redirect: { permanent: false, destination: '/auth/login' } };
|
||||
}
|
||||
const session = await getSession(context);
|
||||
if (!session) {
|
||||
return { redirect: { permanent: false, destination: "/auth/login" } };
|
||||
}
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: session.user.email,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
name: true,
|
||||
email: true,
|
||||
bio: true,
|
||||
avatar: true,
|
||||
timeZone: true,
|
||||
weekStart: true,
|
||||
}
|
||||
});
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: session.user.email,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
name: true,
|
||||
email: true,
|
||||
bio: true,
|
||||
avatar: true,
|
||||
timeZone: true,
|
||||
weekStart: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
props: {user}, // will be passed to the page component as props
|
||||
}
|
||||
return {
|
||||
props: { user }, // will be passed to the page component as props
|
||||
};
|
||||
}
|
|
@ -1,105 +1,114 @@
|
|||
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 Modal from '../../components/Modal';
|
||||
import Shell from '../../components/Shell';
|
||||
import SettingsShell from '../../components/Settings';
|
||||
import Avatar from '../../components/Avatar';
|
||||
import { signIn, useSession, getSession } from 'next-auth/client';
|
||||
import TimezoneSelect from 'react-timezone-select';
|
||||
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";
|
||||
import { useSession, getSession } from "next-auth/client";
|
||||
import Loader from '@components/Loader';
|
||||
|
||||
export default function Embed(props) {
|
||||
const [ session, loading ] = useSession();
|
||||
const router = useRouter();
|
||||
const [session, loading] = useSession();
|
||||
|
||||
if (loading) {
|
||||
return <div className="loader"></div>;
|
||||
}
|
||||
if (loading) {
|
||||
return <Loader/>;
|
||||
}
|
||||
|
||||
return(
|
||||
<Shell heading="Embed">
|
||||
<Head>
|
||||
<title>Embed | Calendso</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<SettingsShell>
|
||||
<div className="py-6 px-4 sm:p-6 lg:pb-8 lg:col-span-9">
|
||||
<div className="mb-6">
|
||||
<h2 className="text-lg leading-6 font-medium text-gray-900">Iframe Embed</h2>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
The easiest way to embed Calendso on your website.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 space-x-4">
|
||||
<div>
|
||||
<label htmlFor="iframe" className="block text-sm font-medium text-gray-700">
|
||||
Standard iframe
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<textarea
|
||||
id="iframe"
|
||||
className="h-32 shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
placeholder="Loading..."
|
||||
defaultValue={'<iframe src="' + props.BASE_URL + '/' + session.user.username + '" frameborder="0" allowfullscreen></iframe>'}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="fullscreen" className="block text-sm font-medium text-gray-700">
|
||||
Responsive full screen iframe
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<textarea
|
||||
id="fullscreen"
|
||||
className="h-32 shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
placeholder="Loading..."
|
||||
defaultValue={'<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Schedule a meeting</title><style>body {margin: 0;}iframe {height: calc(100vh - 4px);width: calc(100vw - 4px);box-sizing: border-box;}</style></head><body><iframe src="' + props.BASE_URL + '/' + session.user.username + '" frameborder="0" allowfullscreen></iframe></body></html>'}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-6">
|
||||
<h2 className="text-lg leading-6 font-medium text-gray-900">Calendso API</h2>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
Leverage our API for full control and customizability.
|
||||
</p>
|
||||
</div>
|
||||
<a href="https://api.docs.calendso.com" className="btn btn-primary">Browse our API documentation</a>
|
||||
</div>
|
||||
</SettingsShell>
|
||||
</Shell>
|
||||
);
|
||||
return (
|
||||
<Shell heading="Embed" subtitle="Integrate with your website using our embed options.">
|
||||
<Head>
|
||||
<title>Embed | Calendso</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<SettingsShell>
|
||||
<div className="py-6 lg:pb-8 lg:col-span-9">
|
||||
<div className="mb-6">
|
||||
<h2 className="text-lg leading-6 font-medium text-gray-900">iframe Embed</h2>
|
||||
<p className="mt-1 text-sm text-gray-500">The easiest way to embed Calendso on your website.</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 space-x-4">
|
||||
<div>
|
||||
<label htmlFor="iframe" className="block text-sm font-medium text-gray-700">
|
||||
Standard iframe
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<textarea
|
||||
id="iframe"
|
||||
className="h-32 shadow-sm focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-sm"
|
||||
placeholder="Loading..."
|
||||
defaultValue={
|
||||
'<iframe src="' +
|
||||
props.BASE_URL +
|
||||
"/" +
|
||||
session.user.username +
|
||||
'" frameborder="0" allowfullscreen></iframe>'
|
||||
}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="fullscreen" className="block text-sm font-medium text-gray-700">
|
||||
Responsive full screen iframe
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<textarea
|
||||
id="fullscreen"
|
||||
className="h-32 shadow-sm focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-sm"
|
||||
placeholder="Loading..."
|
||||
defaultValue={
|
||||
'<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Schedule a meeting</title><style>body {margin: 0;}iframe {height: calc(100vh - 4px);width: calc(100vw - 4px);box-sizing: border-box;}</style></head><body><iframe src="' +
|
||||
props.BASE_URL +
|
||||
"/" +
|
||||
session.user.username +
|
||||
'" frameborder="0" allowfullscreen></iframe></body></html>'
|
||||
}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-6">
|
||||
<h2 className="text-lg leading-6 font-medium text-gray-900">Calendso API</h2>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
Leverage our API for full control and customizability.
|
||||
</p>
|
||||
</div>
|
||||
<a href="https://api.docs.calendso.com" className="btn btn-primary">
|
||||
Browse our API documentation
|
||||
</a>
|
||||
</div>
|
||||
</SettingsShell>
|
||||
</Shell>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
const session = await getSession(context);
|
||||
if (!session) {
|
||||
return { redirect: { permanent: false, destination: '/auth/login' } };
|
||||
}
|
||||
const session = await getSession(context);
|
||||
if (!session) {
|
||||
return { redirect: { permanent: false, destination: "/auth/login" } };
|
||||
}
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: session.user.email,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
name: true,
|
||||
email: true,
|
||||
bio: true,
|
||||
avatar: true,
|
||||
timeZone: true,
|
||||
weekStart: true,
|
||||
}
|
||||
});
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: session.user.email,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
name: true,
|
||||
email: true,
|
||||
bio: true,
|
||||
avatar: true,
|
||||
timeZone: true,
|
||||
weekStart: true,
|
||||
},
|
||||
});
|
||||
|
||||
const BASE_URL = process.env.BASE_URL;
|
||||
const BASE_URL = process.env.BASE_URL;
|
||||
|
||||
return {
|
||||
props: {user, BASE_URL}, // will be passed to the page component as props
|
||||
}
|
||||
return {
|
||||
props: { user, BASE_URL }, // will be passed to the page component as props
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,105 +1,128 @@
|
|||
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 { signIn, useSession, getSession } from 'next-auth/client';
|
||||
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';
|
||||
|
||||
export default function Settings(props) {
|
||||
const [ session, loading ] = useSession();
|
||||
const [successModalOpen, setSuccessModalOpen] = useState(false);
|
||||
const oldPasswordRef = useRef<HTMLInputElement>();
|
||||
const newPasswordRef = useRef<HTMLInputElement>();
|
||||
const [session, loading] = useSession();
|
||||
const [successModalOpen, setSuccessModalOpen] = useState(false);
|
||||
const oldPasswordRef = useRef<HTMLInputElement>();
|
||||
const newPasswordRef = useRef<HTMLInputElement>();
|
||||
|
||||
if (loading) {
|
||||
return <div className="loader"></div>;
|
||||
}
|
||||
if (loading) {
|
||||
return <Loader/>;
|
||||
}
|
||||
|
||||
const closeSuccessModal = () => { setSuccessModalOpen(false); }
|
||||
const closeSuccessModal = () => {
|
||||
setSuccessModalOpen(false);
|
||||
};
|
||||
|
||||
async function changePasswordHandler(event) {
|
||||
event.preventDefault();
|
||||
async function changePasswordHandler(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const enteredOldPassword = oldPasswordRef.current.value;
|
||||
const enteredNewPassword = newPasswordRef.current.value;
|
||||
const enteredOldPassword = oldPasswordRef.current.value;
|
||||
const enteredNewPassword = newPasswordRef.current.value;
|
||||
|
||||
// TODO: Add validation
|
||||
// TODO: Add validation
|
||||
|
||||
const response = await fetch('/api/auth/changepw', {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({oldPassword: enteredOldPassword, newPassword: enteredNewPassword}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
const response = await fetch("/api/auth/changepw", {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify({ oldPassword: enteredOldPassword, newPassword: enteredNewPassword }),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
setSuccessModalOpen(true);
|
||||
}
|
||||
setSuccessModalOpen(true);
|
||||
}
|
||||
|
||||
return(
|
||||
<Shell heading="Password">
|
||||
<Head>
|
||||
<title>Change Password | Calendso</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<SettingsShell>
|
||||
<form className="divide-y divide-gray-200 lg:col-span-9" onSubmit={changePasswordHandler}>
|
||||
<div className="py-6 px-4 sm:p-6 lg:pb-8">
|
||||
<div>
|
||||
<h2 className="text-lg leading-6 font-medium text-gray-900">Change Password</h2>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
Change the password for your Calendso account.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex">
|
||||
<div className="w-1/2 mr-2">
|
||||
<label htmlFor="current_password" className="block text-sm font-medium text-gray-700">Current Password</label>
|
||||
<div className="mt-1">
|
||||
<input ref={oldPasswordRef} type="password" name="current_password" id="current_password" required className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder="Your old password" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-1/2 ml-2">
|
||||
<label htmlFor="new_password" className="block text-sm font-medium text-gray-700">New Password</label>
|
||||
<div className="mt-1">
|
||||
<input ref={newPasswordRef} type="password" name="new_password" id="new_password" required className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder="Your super secure new password" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr className="mt-8" />
|
||||
<div className="py-4 flex justify-end">
|
||||
<button type="submit" className="ml-2 bg-blue-600 border border-transparent rounded-md shadow-sm py-2 px-4 inline-flex justify-center text-sm font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<Modal heading="Password updated successfully" description="Your password has been successfully changed." open={successModalOpen} handleClose={closeSuccessModal} />
|
||||
</SettingsShell>
|
||||
</Shell>
|
||||
);
|
||||
return (
|
||||
<Shell heading="Password" subtitle="Change the password that you use to sign in.">
|
||||
<Head>
|
||||
<title>Change Password | Calendso</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<SettingsShell>
|
||||
<form className="divide-y divide-gray-200 lg:col-span-9" onSubmit={changePasswordHandler}>
|
||||
<div className="py-6 lg:pb-8">
|
||||
<div className="flex">
|
||||
<div className="w-1/2 mr-2">
|
||||
<label htmlFor="current_password" className="block text-sm font-medium text-gray-700">
|
||||
Current Password
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<input
|
||||
ref={oldPasswordRef}
|
||||
type="password"
|
||||
name="current_password"
|
||||
id="current_password"
|
||||
required
|
||||
className="shadow-sm focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-sm"
|
||||
placeholder="Your old password"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-1/2 ml-2">
|
||||
<label htmlFor="new_password" className="block text-sm font-medium text-gray-700">
|
||||
New Password
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<input
|
||||
ref={newPasswordRef}
|
||||
type="password"
|
||||
name="new_password"
|
||||
id="new_password"
|
||||
required
|
||||
className="shadow-sm focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-sm"
|
||||
placeholder="Your super secure new password"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr className="mt-8" />
|
||||
<div className="py-4 flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
className="ml-2 bg-neutral-900 border border-transparent rounded-sm shadow-sm py-2 px-4 inline-flex justify-center text-sm font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<Modal
|
||||
heading="Password updated successfully"
|
||||
description="Your password has been successfully changed."
|
||||
open={successModalOpen}
|
||||
handleClose={closeSuccessModal}
|
||||
/>
|
||||
</SettingsShell>
|
||||
</Shell>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
const session = await getSession(context);
|
||||
if (!session) {
|
||||
return { redirect: { permanent: false, destination: '/auth/login' } };
|
||||
}
|
||||
const session = await getSession(context);
|
||||
if (!session) {
|
||||
return { redirect: { permanent: false, destination: "/auth/login" } };
|
||||
}
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: session.user.email,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
name: true
|
||||
}
|
||||
});
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: session.user.email,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
name: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
props: {user}, // will be passed to the page component as props
|
||||
}
|
||||
return {
|
||||
props: { user }, // will be passed to the page component as props
|
||||
};
|
||||
}
|
|
@ -90,7 +90,7 @@ export default function Settings(props) {
|
|||
}
|
||||
|
||||
return (
|
||||
<Shell heading="Profile">
|
||||
<Shell heading="Profile" subtitle="Edit your profile information, which shows on your scheduling link.">
|
||||
<Head>
|
||||
<title>Profile | Calendso</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
|
@ -98,19 +98,14 @@ export default function Settings(props) {
|
|||
<SettingsShell>
|
||||
<form className="divide-y divide-gray-200 lg:col-span-9" onSubmit={updateProfileHandler}>
|
||||
{hasErrors && <ErrorAlert message={errorMessage} />}
|
||||
<div className="py-6 px-4 sm:p-6 lg:pb-8">
|
||||
<div>
|
||||
<h2 className="text-lg leading-6 font-medium text-gray-900">Profile</h2>
|
||||
<p className="mt-1 text-sm text-gray-500">Review and change your public page details.</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex flex-col lg:flex-row">
|
||||
<div className="py-6 lg:pb-8">
|
||||
<div className="flex flex-col lg:flex-row">
|
||||
<div className="flex-grow space-y-6">
|
||||
<div className="flex">
|
||||
<div className="w-1/2 mr-2">
|
||||
<div className="block sm:flex">
|
||||
<div className="w-full sm:w-1/2 sm:mr-2 mb-6">
|
||||
<UsernameInput ref={usernameRef} defaultValue={props.user.username} />
|
||||
</div>
|
||||
<div className="w-1/2 ml-2">
|
||||
<div className="w-full sm:w-1/2 sm:ml-2">
|
||||
<label htmlFor="name" className="block text-sm font-medium text-gray-700">
|
||||
Full name
|
||||
</label>
|
||||
|
@ -122,7 +117,7 @@ export default function Settings(props) {
|
|||
autoComplete="given-name"
|
||||
placeholder="Your name"
|
||||
required
|
||||
className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||||
className="mt-1 block w-full border border-gray-300 rounded-sm shadow-sm py-2 px-3 focus:outline-none focus:ring-neutral-500 focus:border-neutral-500 sm:text-sm"
|
||||
defaultValue={props.user.name}
|
||||
/>
|
||||
</div>
|
||||
|
@ -140,7 +135,7 @@ export default function Settings(props) {
|
|||
placeholder="A little something about yourself."
|
||||
rows={3}
|
||||
defaultValue={props.user.bio}
|
||||
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md"></textarea>
|
||||
className="shadow-sm focus:ring-neutral-500 focus:border-neutral-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-sm"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
@ -152,7 +147,8 @@ export default function Settings(props) {
|
|||
id="timeZone"
|
||||
value={selectedTimeZone}
|
||||
onChange={setSelectedTimeZone}
|
||||
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
classNamePrefix="react-select"
|
||||
className="react-select-container border border-gray-300 rounded-sm shadow-sm focus:ring-neutral-500 focus:border-neutral-500 mt-1 block w-full sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -165,7 +161,8 @@ export default function Settings(props) {
|
|||
id="weekStart"
|
||||
value={selectedWeekStartDay}
|
||||
onChange={setSelectedWeekStartDay}
|
||||
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
classNamePrefix="react-select"
|
||||
className="react-select-container border border-gray-300 rounded-sm shadow-sm focus:ring-neutral-500 focus:border-neutral-500 mt-1 block w-full sm:text-sm"
|
||||
options={[
|
||||
{ value: "Sunday", label: "Sunday" },
|
||||
{ value: "Monday", label: "Monday" },
|
||||
|
@ -183,11 +180,11 @@ export default function Settings(props) {
|
|||
isDisabled={!selectedTheme}
|
||||
defaultValue={selectedTheme || themeOptions[0]}
|
||||
onChange={setSelectedTheme}
|
||||
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
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}
|
||||
/>
|
||||
</div>
|
||||
<div className="relative flex items-start">
|
||||
<div className="mt-8 relative flex items-start">
|
||||
<div className="flex items-center h-5">
|
||||
<input
|
||||
id="theme-adjust-os"
|
||||
|
@ -195,7 +192,7 @@ export default function Settings(props) {
|
|||
type="checkbox"
|
||||
onChange={(e) => setSelectedTheme(e.target.checked ? null : themeOptions[0])}
|
||||
defaultChecked={!selectedTheme}
|
||||
className="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded"
|
||||
className="focus:ring-neutral-500 h-4 w-4 text-neutral-900 border-gray-300 rounded-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3 text-sm">
|
||||
|
@ -214,7 +211,7 @@ export default function Settings(props) {
|
|||
type="checkbox"
|
||||
ref={hideBrandingRef}
|
||||
defaultChecked={props.user.hideBranding}
|
||||
className="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded"
|
||||
className="focus:ring-neutral-500 h-4 w-4 text-neutral-900 border-gray-300 rounded-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3 text-sm">
|
||||
|
@ -238,13 +235,13 @@ export default function Settings(props) {
|
|||
aria-hidden="true">
|
||||
<Avatar user={props.user} className="rounded-full h-full w-full" />
|
||||
</div>
|
||||
{/* <div className="ml-5 rounded-md shadow-sm">
|
||||
<div className="group relative border border-gray-300 rounded-md py-2 px-3 flex items-center justify-center hover:bg-gray-50 focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-blue-500">
|
||||
{/* <div className="ml-5 rounded-sm shadow-sm">
|
||||
<div className="group relative border border-gray-300 rounded-sm py-2 px-3 flex items-center justify-center hover:bg-gray-50 focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-neutral-500">
|
||||
<label htmlFor="user_photo" className="relative text-sm leading-4 font-medium text-gray-700 pointer-events-none">
|
||||
<span>Change</span>
|
||||
<span className="sr-only"> user photo</span>
|
||||
</label>
|
||||
<input id="user_photo" name="user_photo" type="file" className="absolute w-full h-full opacity-0 cursor-pointer border-gray-300 rounded-md" />
|
||||
<input id="user_photo" name="user_photo" type="file" className="absolute w-full h-full opacity-0 cursor-pointer border-gray-300 rounded-sm" />
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
|
@ -254,12 +251,12 @@ export default function Settings(props) {
|
|||
<Avatar
|
||||
user={props.user}
|
||||
className="relative rounded-full w-40 h-40"
|
||||
fallback={<div className="relative bg-blue-600 rounded-full w-40 h-40"></div>}
|
||||
fallback={<div className="relative bg-neutral-900 rounded-full w-40 h-40"></div>}
|
||||
/>
|
||||
{/* <label htmlFor="user-photo" className="absolute inset-0 w-full h-full bg-black bg-opacity-75 flex items-center justify-center text-sm font-medium text-white opacity-0 hover:opacity-100 focus-within:opacity-100">
|
||||
<span>Change</span>
|
||||
<span className="sr-only"> user photo</span>
|
||||
<input type="file" id="user-photo" name="user-photo" className="absolute inset-0 w-full h-full opacity-0 cursor-pointer border-gray-300 rounded-md" />
|
||||
<input type="file" id="user-photo" name="user-photo" className="absolute inset-0 w-full h-full opacity-0 cursor-pointer border-gray-300 rounded-sm" />
|
||||
</label> */}
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
|
@ -272,7 +269,7 @@ export default function Settings(props) {
|
|||
name="avatar"
|
||||
id="avatar"
|
||||
placeholder="URL"
|
||||
className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||||
className="mt-1 block w-full border border-gray-300 rounded-sm shadow-sm py-2 px-3 focus:outline-none focus:ring-neutral-500 focus:border-neutral-500 sm:text-sm"
|
||||
defaultValue={props.user.avatar}
|
||||
/>
|
||||
</div>
|
||||
|
@ -282,7 +279,7 @@ export default function Settings(props) {
|
|||
<div className="py-4 flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
className="ml-2 bg-blue-600 border border-transparent rounded-md shadow-sm py-2 px-4 inline-flex justify-center text-sm font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
className="ml-2 bg-neutral-900 border border-transparent rounded-sm shadow-sm py-2 px-4 inline-flex justify-center text-sm font-medium text-white hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-500">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -57,22 +57,18 @@ export default function Teams() {
|
|||
};
|
||||
|
||||
return (
|
||||
<Shell heading="Teams">
|
||||
<Shell heading="Teams" subtitle="Create and manage teams to use collaborative features.">
|
||||
<Head>
|
||||
<title>Teams | Calendso</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<SettingsShell>
|
||||
<div className="divide-y divide-gray-200 lg:col-span-9">
|
||||
<div className="py-6 px-4 sm:p-6 lg:pb-8">
|
||||
<div className="py-6 lg:pb-8">
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<h2 className="text-lg leading-6 font-medium text-gray-900">Your teams</h2>
|
||||
<p className="mt-1 text-sm text-gray-500 mb-4">
|
||||
View, edit and create teams to organise relationships between users
|
||||
</p>
|
||||
{!(invites.length || teams.length) && (
|
||||
<div className="bg-gray-50 sm:rounded-lg">
|
||||
<div className="bg-gray-50 sm:rounded-sm">
|
||||
<div className="px-4 py-5 sm:p-6">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">
|
||||
Create a team to get started
|
||||
|
@ -94,7 +90,7 @@ export default function Teams() {
|
|||
</div>
|
||||
{!!(invites.length || teams.length) && (
|
||||
<div>
|
||||
<button className="btn-sm btn-primary" onClick={() => setShowCreateTeamModal(true)}>
|
||||
<button className="btn-sm btn-primary mb-4" onClick={() => setShowCreateTeamModal(true)}>
|
||||
Create new team
|
||||
</button>
|
||||
</div>
|
||||
|
@ -143,10 +139,10 @@ export default function Teams() {
|
|||
​
|
||||
</span>
|
||||
|
||||
<div className="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6">
|
||||
<div className="inline-block align-bottom bg-white rounded-sm px-4 pt-5 pb-4 text-left 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 mb-4">
|
||||
<div className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<UsersIcon className="h-6 w-6 text-blue-600" />
|
||||
<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">
|
||||
<UsersIcon 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">
|
||||
|
@ -168,7 +164,7 @@ export default function Teams() {
|
|||
id="name"
|
||||
placeholder="Acme Inc."
|
||||
required
|
||||
className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||||
className="mt-1 block w-full border border-gray-300 rounded-sm shadow-sm py-2 px-3 focus:outline-none focus:ring-neutral-500 focus:border-neutral-500 sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
|
|
|
@ -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:#26282C;}
|
||||
.st0{fillRule:evenodd;clipRule:evenodd;fill:#26282C;}
|
||||
</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.3c4.7,3.6,8,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.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
@ -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:#fff;}
|
||||
.st0{fillRule:evenodd;clipRule:evenodd;fill:#fff;}
|
||||
</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 |
|
@ -1 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 566.93 106.87"><defs><style>.cls-1,.cls-2{fill:#fff;}.cls-1,.cls-3{fill-rule:evenodd;}.cls-3{fill:#2492eb;}</style></defs><path class="cls-1" d="M178.41,87.12a28.08,28.08,0,0,1-14-3.59,25.91,25.91,0,0,1-9.94-10,29.62,29.62,0,0,1,0-28.25,25.88,25.88,0,0,1,10-10,28,28,0,0,1,14-3.61A26.53,26.53,0,0,1,195,37q7,5.33,9.86,15.41H193.68a15.82,15.82,0,0,0-6.17-7.16,16.32,16.32,0,0,0-8.68-2.46,17.22,17.22,0,0,0-8.07,2,15.63,15.63,0,0,0-6.14,5.81,17.85,17.85,0,0,0-.19,17.26,16.41,16.41,0,0,0,5.79,6,14.94,14.94,0,0,0,8,2.23c7.23,0,12.37-3.29,15.37-9.89h11.35a31.36,31.36,0,0,1-5.77,11.52,24.73,24.73,0,0,1-9.16,7,28.61,28.61,0,0,1-11.59,2.34ZM250.48,81a24.7,24.7,0,0,1-16.77,6.15,28,28,0,0,1-14-3.59,26.15,26.15,0,0,1-9.84-10A29.21,29.21,0,0,1,206.22,59a27.1,27.1,0,0,1,27.2-27.29,25.66,25.66,0,0,1,17.49,6.25V32.72h11.5V86.07H250.48V81ZM234.34,42.66a16.53,16.53,0,0,0-8.42,2.2,16.21,16.21,0,0,0-6,6,16.38,16.38,0,0,0-2.22,8.37,16.59,16.59,0,0,0,4.71,12.16,16.38,16.38,0,0,0,12.11,4.75,16,16,0,0,0,8.27-2.19,16.31,16.31,0,0,0,5.94-6,17.58,17.58,0,0,0,0-17,16.15,16.15,0,0,0-6-6,16.33,16.33,0,0,0-8.4-2.22Zm38.57-28.87h9.67V86h-9.67V13.79Zm27.77,50.36a15.34,15.34,0,0,0,3,6.27,14.48,14.48,0,0,0,5.36,4.14,16.65,16.65,0,0,0,7,1.47,16.84,16.84,0,0,0,8.1-1.75,14.78,14.78,0,0,0,5.55-5.89h11.87q-4.08,9.74-10.36,14.24a25.3,25.3,0,0,1-15.16,4.49,27.13,27.13,0,0,1-13.8-3.59,25.74,25.74,0,0,1-9.74-10A28.65,28.65,0,0,1,289,59.4a28.65,28.65,0,0,1,3.51-14.09,25.62,25.62,0,0,1,9.79-10,28.15,28.15,0,0,1,27.74.09,25.36,25.36,0,0,1,9.57,10.24,30.18,30.18,0,0,1,3.26,14c0,1-.1,2.47-.29,4.51ZM316.1,42.53a15.83,15.83,0,0,0-9.56,2.92,14.91,14.91,0,0,0-5.58,8h30.18a15.2,15.2,0,0,0-5.59-8,15.63,15.63,0,0,0-9.45-2.92Zm69.63,13.73q0-7-3-10.46c-2-2.35-4.89-3.51-8.68-3.51a11.46,11.46,0,0,0-6.48,2,14.08,14.08,0,0,0-4.76,5.36A15.6,15.6,0,0,0,361,57.08V86H349.79V32.72h10.46v4.76a20.2,20.2,0,0,1,14.87-5.82,20.87,20.87,0,0,1,11,3,21.54,21.54,0,0,1,7.89,8.21,23.45,23.45,0,0,1,2.88,11.55V86h-11.2V56.26Zm61.81,24.91a25.73,25.73,0,0,1-7.91,4.44,28,28,0,0,1-9.35,1.51,26.4,26.4,0,0,1-13.75-3.69,26.94,26.94,0,0,1-9.78-10.07,28.29,28.29,0,0,1-3.57-14.06,30.39,30.39,0,0,1,3.46-14.67,26.14,26.14,0,0,1,9.79-10.14,27.27,27.27,0,0,1,14.14-3.69,28.53,28.53,0,0,1,9,1.4,25.51,25.51,0,0,1,7.78,4.08V13.84h11.3V86h-11.1V81.17ZM431.24,42a16.34,16.34,0,0,0-8.41,2.22,16.05,16.05,0,0,0-6,6,16.75,16.75,0,0,0-2.18,8.49,17.11,17.11,0,0,0,4.71,12.4,16.07,16.07,0,0,0,12.06,4.8,15.71,15.71,0,0,0,8.31-2.26,16.37,16.37,0,0,0,5.94-6.14,17.92,17.92,0,0,0,0-17.17A16.44,16.44,0,0,0,431.24,42Zm55.44,45.13a23.84,23.84,0,0,1-10.84-2.39,18.14,18.14,0,0,1-7.46-7,22.68,22.68,0,0,1-3-10.94h11a10,10,0,0,0,3.08,7.12A11,11,0,0,0,487,76.41a15.51,15.51,0,0,0,5.33-.82,7.27,7.27,0,0,0,3.31-2.18,4.68,4.68,0,0,0,1.1-3,4.06,4.06,0,0,0-1.24-3.19,8.52,8.52,0,0,0-1.72-1.16,13.45,13.45,0,0,0-2.17-.81c-1.82-.44-3.87-.86-6.15-1.29-1.76-.25-3.46-.58-5.1-1s-3.47-1-5-1.59A11.16,11.16,0,0,1,473,60.35l-1-.71a9.64,9.64,0,0,0-1.2-1,8.44,8.44,0,0,1-1.77-1.82,17.51,17.51,0,0,1-1.49-2.61,14.27,14.27,0,0,1-1.25-6.43A14.21,14.21,0,0,1,469,39.29a16.77,16.77,0,0,1,7.2-5.65,25.49,25.49,0,0,1,10.29-2A22.09,22.09,0,0,1,496.82,34a17.22,17.22,0,0,1,7.12,6.73,21.13,21.13,0,0,1,2.74,10.4H496c-.14-3-1.05-5.22-2.69-6.73s-4-2.26-7-2.26-5.24.51-6.76,1.54a4.49,4.49,0,0,0-2.28,3.84,3.12,3.12,0,0,0,1,2.64,8.49,8.49,0,0,0,1.84,1.18,10,10,0,0,0,2,.66c.81.21,1.69.43,2.69.62l3.12.53a65.57,65.57,0,0,1,10,2.21,14,14,0,0,1,6.74,4.46,14.62,14.62,0,0,1,3,8.37l.1,1.82A15.47,15.47,0,0,1,505,79.1a17.09,17.09,0,0,1-7.46,6,27.44,27.44,0,0,1-10.9,2.06Zm76.61-13.55a26.23,26.23,0,0,1-10.19,10,30.43,30.43,0,0,1-14.77,3.59,27.65,27.65,0,0,1-14-3.64,27.26,27.26,0,0,1-10.08-9.92,26.66,26.66,0,0,1-3.73-13.83,28.7,28.7,0,0,1,3.61-14.56,25.87,25.87,0,0,1,10.24-10,29.05,29.05,0,0,1,14.32-3.56,29.59,29.59,0,0,1,14.32,3.61,26.71,26.71,0,0,1,10.24,9.9,28.3,28.3,0,0,1,3.64,14.23,28.21,28.21,0,0,1-3.64,14.17ZM538.73,42.29a17.06,17.06,0,0,0-8.63,2.26,16.7,16.7,0,0,0-6.28,6.15,16.46,16.46,0,0,0-2.3,8.5,16.65,16.65,0,0,0,5.18,12.16A17,17,0,0,0,539,76.71a16.16,16.16,0,0,0,8.53-2.37A17.52,17.52,0,0,0,553.72,68,17,17,0,0,0,556,59.4a16.38,16.38,0,0,0-2.33-8.58,17.24,17.24,0,0,0-6.3-6.22A16.89,16.89,0,0,0,538.73,42.29Z" transform="translate(0)"/><rect class="cls-2" x="19.35" width="10.11" height="18.5" rx="5.05"/><rect class="cls-2" x="68.31" width="10.11" height="18.5" rx="5.05"/><path class="cls-1" d="M11,9.1h3v4.35A10.43,10.43,0,0,0,24.4,23.84h0A10.42,10.42,0,0,0,34.79,13.45V9.1H63v4.35A10.42,10.42,0,0,0,73.36,23.84h0A10.42,10.42,0,0,0,83.75,13.45V9.1h3a11,11,0,0,1,11,11V95.91a11,11,0,0,1-11,11H48.54L0,58.33V20.07a11,11,0,0,1,11-11ZM68.21,33.64H29.31a5,5,0,0,0-5,5v38.9a5,5,0,0,0,5,5H61.1L73.24,94.74V38.67A5,5,0,0,0,68.21,33.64Z" transform="translate(0)"/><polygon class="cls-3" points="48.54 58.33 0 58.33 48.54 106.87 48.54 58.33"/></svg>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 566.93 106.87"><defs><style>.cls-1,.cls-2{fill:#fff;}.cls-1,.cls-3{fillRule:evenodd;}.cls-3{fill:#2492eb;}</style></defs><path class="cls-1" d="M178.41,87.12a28.08,28.08,0,0,1-14-3.59,25.91,25.91,0,0,1-9.94-10,29.62,29.62,0,0,1,0-28.25,25.88,25.88,0,0,1,10-10,28,28,0,0,1,14-3.61A26.53,26.53,0,0,1,195,37q7,5.33,9.86,15.41H193.68a15.82,15.82,0,0,0-6.17-7.16,16.32,16.32,0,0,0-8.68-2.46,17.22,17.22,0,0,0-8.07,2,15.63,15.63,0,0,0-6.14,5.81,17.85,17.85,0,0,0-.19,17.26,16.41,16.41,0,0,0,5.79,6,14.94,14.94,0,0,0,8,2.23c7.23,0,12.37-3.29,15.37-9.89h11.35a31.36,31.36,0,0,1-5.77,11.52,24.73,24.73,0,0,1-9.16,7,28.61,28.61,0,0,1-11.59,2.34ZM250.48,81a24.7,24.7,0,0,1-16.77,6.15,28,28,0,0,1-14-3.59,26.15,26.15,0,0,1-9.84-10A29.21,29.21,0,0,1,206.22,59a27.1,27.1,0,0,1,27.2-27.29,25.66,25.66,0,0,1,17.49,6.25V32.72h11.5V86.07H250.48V81ZM234.34,42.66a16.53,16.53,0,0,0-8.42,2.2,16.21,16.21,0,0,0-6,6,16.38,16.38,0,0,0-2.22,8.37,16.59,16.59,0,0,0,4.71,12.16,16.38,16.38,0,0,0,12.11,4.75,16,16,0,0,0,8.27-2.19,16.31,16.31,0,0,0,5.94-6,17.58,17.58,0,0,0,0-17,16.15,16.15,0,0,0-6-6,16.33,16.33,0,0,0-8.4-2.22Zm38.57-28.87h9.67V86h-9.67V13.79Zm27.77,50.36a15.34,15.34,0,0,0,3,6.27,14.48,14.48,0,0,0,5.36,4.14,16.65,16.65,0,0,0,7,1.47,16.84,16.84,0,0,0,8.1-1.75,14.78,14.78,0,0,0,5.55-5.89h11.87q-4.08,9.74-10.36,14.24a25.3,25.3,0,0,1-15.16,4.49,27.13,27.13,0,0,1-13.8-3.59,25.74,25.74,0,0,1-9.74-10A28.65,28.65,0,0,1,289,59.4a28.65,28.65,0,0,1,3.51-14.09,25.62,25.62,0,0,1,9.79-10,28.15,28.15,0,0,1,27.74.09,25.36,25.36,0,0,1,9.57,10.24,30.18,30.18,0,0,1,3.26,14c0,1-.1,2.47-.29,4.51ZM316.1,42.53a15.83,15.83,0,0,0-9.56,2.92,14.91,14.91,0,0,0-5.58,8h30.18a15.2,15.2,0,0,0-5.59-8,15.63,15.63,0,0,0-9.45-2.92Zm69.63,13.73q0-7-3-10.46c-2-2.35-4.89-3.51-8.68-3.51a11.46,11.46,0,0,0-6.48,2,14.08,14.08,0,0,0-4.76,5.36A15.6,15.6,0,0,0,361,57.08V86H349.79V32.72h10.46v4.76a20.2,20.2,0,0,1,14.87-5.82,20.87,20.87,0,0,1,11,3,21.54,21.54,0,0,1,7.89,8.21,23.45,23.45,0,0,1,2.88,11.55V86h-11.2V56.26Zm61.81,24.91a25.73,25.73,0,0,1-7.91,4.44,28,28,0,0,1-9.35,1.51,26.4,26.4,0,0,1-13.75-3.69,26.94,26.94,0,0,1-9.78-10.07,28.29,28.29,0,0,1-3.57-14.06,30.39,30.39,0,0,1,3.46-14.67,26.14,26.14,0,0,1,9.79-10.14,27.27,27.27,0,0,1,14.14-3.69,28.53,28.53,0,0,1,9,1.4,25.51,25.51,0,0,1,7.78,4.08V13.84h11.3V86h-11.1V81.17ZM431.24,42a16.34,16.34,0,0,0-8.41,2.22,16.05,16.05,0,0,0-6,6,16.75,16.75,0,0,0-2.18,8.49,17.11,17.11,0,0,0,4.71,12.4,16.07,16.07,0,0,0,12.06,4.8,15.71,15.71,0,0,0,8.31-2.26,16.37,16.37,0,0,0,5.94-6.14,17.92,17.92,0,0,0,0-17.17A16.44,16.44,0,0,0,431.24,42Zm55.44,45.13a23.84,23.84,0,0,1-10.84-2.39,18.14,18.14,0,0,1-7.46-7,22.68,22.68,0,0,1-3-10.94h11a10,10,0,0,0,3.08,7.12A11,11,0,0,0,487,76.41a15.51,15.51,0,0,0,5.33-.82,7.27,7.27,0,0,0,3.31-2.18,4.68,4.68,0,0,0,1.1-3,4.06,4.06,0,0,0-1.24-3.19,8.52,8.52,0,0,0-1.72-1.16,13.45,13.45,0,0,0-2.17-.81c-1.82-.44-3.87-.86-6.15-1.29-1.76-.25-3.46-.58-5.1-1s-3.47-1-5-1.59A11.16,11.16,0,0,1,473,60.35l-1-.71a9.64,9.64,0,0,0-1.2-1,8.44,8.44,0,0,1-1.77-1.82,17.51,17.51,0,0,1-1.49-2.61,14.27,14.27,0,0,1-1.25-6.43A14.21,14.21,0,0,1,469,39.29a16.77,16.77,0,0,1,7.2-5.65,25.49,25.49,0,0,1,10.29-2A22.09,22.09,0,0,1,496.82,34a17.22,17.22,0,0,1,7.12,6.73,21.13,21.13,0,0,1,2.74,10.4H496c-.14-3-1.05-5.22-2.69-6.73s-4-2.26-7-2.26-5.24.51-6.76,1.54a4.49,4.49,0,0,0-2.28,3.84,3.12,3.12,0,0,0,1,2.64,8.49,8.49,0,0,0,1.84,1.18,10,10,0,0,0,2,.66c.81.21,1.69.43,2.69.62l3.12.53a65.57,65.57,0,0,1,10,2.21,14,14,0,0,1,6.74,4.46,14.62,14.62,0,0,1,3,8.37l.1,1.82A15.47,15.47,0,0,1,505,79.1a17.09,17.09,0,0,1-7.46,6,27.44,27.44,0,0,1-10.9,2.06Zm76.61-13.55a26.23,26.23,0,0,1-10.19,10,30.43,30.43,0,0,1-14.77,3.59,27.65,27.65,0,0,1-14-3.64,27.26,27.26,0,0,1-10.08-9.92,26.66,26.66,0,0,1-3.73-13.83,28.7,28.7,0,0,1,3.61-14.56,25.87,25.87,0,0,1,10.24-10,29.05,29.05,0,0,1,14.32-3.56,29.59,29.59,0,0,1,14.32,3.61,26.71,26.71,0,0,1,10.24,9.9,28.3,28.3,0,0,1,3.64,14.23,28.21,28.21,0,0,1-3.64,14.17ZM538.73,42.29a17.06,17.06,0,0,0-8.63,2.26,16.7,16.7,0,0,0-6.28,6.15,16.46,16.46,0,0,0-2.3,8.5,16.65,16.65,0,0,0,5.18,12.16A17,17,0,0,0,539,76.71a16.16,16.16,0,0,0,8.53-2.37A17.52,17.52,0,0,0,553.72,68,17,17,0,0,0,556,59.4a16.38,16.38,0,0,0-2.33-8.58,17.24,17.24,0,0,0-6.3-6.22A16.89,16.89,0,0,0,538.73,42.29Z" transform="translate(0)"/><rect class="cls-2" x="19.35" width="10.11" height="18.5" rx="5.05"/><rect class="cls-2" x="68.31" width="10.11" height="18.5" rx="5.05"/><path class="cls-1" d="M11,9.1h3v4.35A10.43,10.43,0,0,0,24.4,23.84h0A10.42,10.42,0,0,0,34.79,13.45V9.1H63v4.35A10.42,10.42,0,0,0,73.36,23.84h0A10.42,10.42,0,0,0,83.75,13.45V9.1h3a11,11,0,0,1,11,11V95.91a11,11,0,0,1-11,11H48.54L0,58.33V20.07a11,11,0,0,1,11-11ZM68.21,33.64H29.31a5,5,0,0,0-5,5v38.9a5,5,0,0,0,5,5H61.1L73.24,94.74V38.67A5,5,0,0,0,68.21,33.64Z" transform="translate(0)"/><polygon class="cls-3" points="48.54 58.33 0 58.33 48.54 106.87 48.54 58.33"/></svg>
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 5.8 KiB |
18
public/integrations/go-to-meeting.svg
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 29.2 33" style="enable-background:new 0 0 29.2 33;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#F68D2E;}
|
||||
</style>
|
||||
<path class="st0" d="M27.2,18.5c-1.5-0.9-3.3-0.8-4.7,0.2c-0.3,0.2-0.6,0.2-0.9,0l-4.2-2.4c-0.1,0-0.1-0.1,0-0.1c0,0,0,0,0,0
|
||||
l4.2-2.4c0.3-0.2,0.7-0.1,0.9,0.1c1.4,1,3.2,1.1,4.7,0.2c1.9-1.2,2.6-3.7,1.5-5.7c-1.1-2.1-3.7-2.8-5.8-1.7
|
||||
c-1.5,0.8-2.4,2.4-2.2,4.1c0,0.3-0.1,0.7-0.4,0.8L16,13.9c-0.1,0-0.1,0-0.1,0c0,0,0,0,0-0.1V8.9c0-0.3,0.2-0.6,0.5-0.8
|
||||
c2.1-1,3.1-3.5,2.1-5.7S15-0.6,12.8,0.4S9.7,3.9,10.7,6c0.4,0.9,1.2,1.7,2.1,2.1c0.3,0.1,0.5,0.4,0.5,0.8v4.9c0,0.1,0,0.1-0.1,0.1
|
||||
c0,0,0,0-0.1,0l-4.2-2.4c-0.3-0.2-0.5-0.5-0.4-0.8C8.7,8.3,7,6.2,4.6,6S0.2,7.6,0,9.9s1.5,4.4,3.9,4.6c1,0.1,2-0.2,2.9-0.8
|
||||
c0.3-0.2,0.6-0.2,0.9-0.1l4.2,2.4c0.1,0,0.1,0.1,0,0.1c0,0,0,0,0,0l-4.2,2.4c-0.3,0.2-0.7,0.1-0.9,0c-1.4-1-3.2-1-4.7-0.2
|
||||
c-1.9,1.2-2.6,3.7-1.5,5.7c1.1,2.1,3.7,2.8,5.8,1.7c1.5-0.8,2.4-2.4,2.2-4.1c0-0.3,0.1-0.7,0.4-0.8l4.2-2.4c0.1,0,0.1,0,0.1,0
|
||||
c0,0,0,0,0,0.1v4.9c0,0.3-0.2,0.6-0.5,0.8c-2.1,1-3.1,3.5-2.1,5.7s3.5,3.1,5.7,2.1c2.1-1,3.1-3.5,2.1-5.7c-0.4-0.9-1.2-1.7-2.1-2.1
|
||||
c-0.3-0.1-0.5-0.4-0.5-0.8v-4.9c0-0.1,0-0.1,0.1-0.1c0,0,0,0,0.1,0l4.2,2.4c0.3,0.2,0.5,0.5,0.4,0.8c-0.2,2.3,1.5,4.4,3.9,4.6
|
||||
c1.7,0.2,3.3-0.7,4.1-2.2C29.8,22.2,29.1,19.7,27.2,18.5L27.2,18.5z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 26 KiB |
1
public/integrations/google-calendar.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="186 38 76 76"><path fill="#fff" d="M244 56h-40v40h40V56z"/><path fill="#EA4335" d="M244 114l18-18h-18v18z"/><path fill="#FBBC04" d="M262 56h-18v40h18V56z"/><path fill="#34A853" d="M244 96h-40v18h40V96z"/><path fill="#188038" d="M186 96v12c0 3.315 2.685 6 6 6h12V96h-18z"/><path fill="#1967D2" d="M262 56V44c0-3.315-2.685-6-6-6h-12v18h18z"/><path fill="#4285F4" d="M244 38h-52c-3.315 0 -6 2.685-6 6v52h18V56h40V38z"/><path fill="#4285F4" d="M212.205 87.03c-1.495-1.01-2.53-2.485-3.095-4.435l3.47-1.43c.315 1.2.865 2.13 1.65 2.79.78.66 1.73.985 2.84.985 1.135 0 2.11-.345 2.925-1.035s1.225-1.57 1.225-2.635c0-1.09-.43-1.98-1.29-2.67-.86-.69-1.94-1.035-3.23-1.035h-2.005V74.13h1.8c1.11 0 2.045-.3 2.805-.9.76-.6 1.14-1.42 1.14-2.465 0 -.93-.34-1.67-1.02-2.225-.68-.555-1.54-.835-2.585-.835-1.02 0 -1.83.27-2.43.815a4.784 4.784 0 0 0 -1.31 2.005l-3.435-1.43c.455-1.29 1.29-2.43 2.515-3.415 1.225-.985 2.79-1.48 4.69-1.48 1.405 0 2.67.27 3.79.815 1.12.545 2 1.3 2.635 2.26.635.965.95 2.045.95 3.245 0 1.225-.295 2.26-.885 3.11-.59.85-1.315 1.5-2.175 1.955v.205a6.605 6.605 0 0 1 2.79 2.175c.725.975 1.09 2.14 1.09 3.5 0 1.36-.345 2.575-1.035 3.64s-1.645 1.905-2.855 2.515c-1.215.61-2.58.92-4.095.92-1.755.005-3.375-.5-4.87-1.51zM233.52 69.81l-3.81 2.755-1.905-2.89 6.835-4.93h2.62V88h-3.74V69.81z"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 19 KiB |
59
public/integrations/microsoft-teams.svg
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
|
||||
<!ENTITY ns_extend "http://ns.adobe.com/Extensibility/1.0/">
|
||||
<!ENTITY ns_ai "http://ns.adobe.com/AdobeIllustrator/10.0/">
|
||||
<!ENTITY ns_graphs "http://ns.adobe.com/Graphs/1.0/">
|
||||
<!ENTITY ns_vars "http://ns.adobe.com/Variables/1.0/">
|
||||
<!ENTITY ns_imrep "http://ns.adobe.com/ImageReplacement/1.0/">
|
||||
<!ENTITY ns_sfw "http://ns.adobe.com/SaveForWeb/1.0/">
|
||||
<!ENTITY ns_custom "http://ns.adobe.com/GenericCustomNamespace/1.0/">
|
||||
<!ENTITY ns_adobe_xpath "http://ns.adobe.com/XPath/1.0/">
|
||||
]>
|
||||
<svg version="1.1" id="Livello_1" xmlns:x="&ns_extend;" xmlns:i="&ns_ai;" xmlns:graph="&ns_graphs;"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 2228.833 2073.333"
|
||||
enable-background="new 0 0 2228.833 2073.333" xml:space="preserve">
|
||||
<metadata>
|
||||
<sfw xmlns="&ns_sfw;">
|
||||
<slices></slices>
|
||||
<sliceSourceBounds bottomLeftOrigin="true" height="2073.333" width="2228.833" x="-1116.333" y="-1012.667">
|
||||
</sliceSourceBounds>
|
||||
</sfw>
|
||||
</metadata>
|
||||
<path fill="#5059C9" d="M1554.637,777.5h575.713c54.391,0,98.483,44.092,98.483,98.483c0,0,0,0,0,0v524.398
|
||||
c0,199.901-162.051,361.952-361.952,361.952h0h-1.711c-199.901,0.028-361.975-162-362.004-361.901c0-0.017,0-0.034,0-0.052V828.971
|
||||
C1503.167,800.544,1526.211,777.5,1554.637,777.5L1554.637,777.5z"/>
|
||||
<circle fill="#5059C9" cx="1943.75" cy="440.583" r="233.25"/>
|
||||
<circle fill="#7B83EB" cx="1218.083" cy="336.917" r="336.917"/>
|
||||
<path fill="#7B83EB" d="M1667.323,777.5H717.01c-53.743,1.33-96.257,45.931-95.01,99.676v598.105
|
||||
c-7.505,322.519,247.657,590.16,570.167,598.053c322.51-7.893,577.671-275.534,570.167-598.053V877.176
|
||||
C1763.579,823.431,1721.066,778.83,1667.323,777.5z"/>
|
||||
<path opacity="0.1" enable-background="new " d="M1244,777.5v838.145c-0.258,38.435-23.549,72.964-59.09,87.598
|
||||
c-11.316,4.787-23.478,7.254-35.765,7.257H667.613c-6.738-17.105-12.958-34.21-18.142-51.833
|
||||
c-18.144-59.477-27.402-121.307-27.472-183.49V877.02c-1.246-53.659,41.198-98.19,94.855-99.52H1244z"/>
|
||||
<path opacity="0.2" enable-background="new " d="M1192.167,777.5v889.978c-0.002,12.287-2.47,24.449-7.257,35.765
|
||||
c-14.634,35.541-49.163,58.833-87.598,59.09H691.975c-8.812-17.105-17.105-34.21-24.362-51.833
|
||||
c-7.257-17.623-12.958-34.21-18.142-51.833c-18.144-59.476-27.402-121.307-27.472-183.49V877.02
|
||||
c-1.246-53.659,41.198-98.19,94.855-99.52H1192.167z"/>
|
||||
<path opacity="0.2" enable-background="new " d="M1192.167,777.5v786.312c-0.395,52.223-42.632,94.46-94.855,94.855h-447.84
|
||||
c-18.144-59.476-27.402-121.307-27.472-183.49V877.02c-1.246-53.659,41.198-98.19,94.855-99.52H1192.167z"/>
|
||||
<path opacity="0.2" enable-background="new " d="M1140.333,777.5v786.312c-0.395,52.223-42.632,94.46-94.855,94.855H649.472
|
||||
c-18.144-59.476-27.402-121.307-27.472-183.49V877.02c-1.246-53.659,41.198-98.19,94.855-99.52H1140.333z"/>
|
||||
<path opacity="0.1" enable-background="new " d="M1244,509.522v163.275c-8.812,0.518-17.105,1.037-25.917,1.037
|
||||
c-8.812,0-17.105-0.518-25.917-1.037c-17.496-1.161-34.848-3.937-51.833-8.293c-104.963-24.857-191.679-98.469-233.25-198.003
|
||||
c-7.153-16.715-12.706-34.071-16.587-51.833h258.648C1201.449,414.866,1243.801,457.217,1244,509.522z"/>
|
||||
<path opacity="0.2" enable-background="new " d="M1192.167,561.355v111.442c-17.496-1.161-34.848-3.937-51.833-8.293
|
||||
c-104.963-24.857-191.679-98.469-233.25-198.003h190.228C1149.616,466.699,1191.968,509.051,1192.167,561.355z"/>
|
||||
<path opacity="0.2" enable-background="new " d="M1192.167,561.355v111.442c-17.496-1.161-34.848-3.937-51.833-8.293
|
||||
c-104.963-24.857-191.679-98.469-233.25-198.003h190.228C1149.616,466.699,1191.968,509.051,1192.167,561.355z"/>
|
||||
<path opacity="0.2" enable-background="new " d="M1140.333,561.355v103.148c-104.963-24.857-191.679-98.469-233.25-198.003
|
||||
h138.395C1097.783,466.699,1140.134,509.051,1140.333,561.355z"/>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="198.099" y1="1683.0726" x2="942.2344" y2="394.2607" gradientTransform="matrix(1 0 0 -1 0 2075.3333)">
|
||||
<stop offset="0" style="stop-color:#5A62C3"/>
|
||||
<stop offset="0.5" style="stop-color:#4D55BD"/>
|
||||
<stop offset="1" style="stop-color:#3940AB"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_1_)" d="M95.01,466.5h950.312c52.473,0,95.01,42.538,95.01,95.01v950.312c0,52.473-42.538,95.01-95.01,95.01
|
||||
H95.01c-52.473,0-95.01-42.538-95.01-95.01V561.51C0,509.038,42.538,466.5,95.01,466.5z"/>
|
||||
<path fill="#FFFFFF" d="M820.211,828.193H630.241v517.297H509.211V828.193H320.123V727.844h500.088V828.193z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 23 KiB |
36
public/integrations/outlook.svg
Normal file
|
@ -0,0 +1,36 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="17 34 127 113">
|
||||
<defs>
|
||||
<linearGradient id="a" x1="28.286" y1="53.757" x2="70.714" y2="127.243" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#1784d9"/>
|
||||
<stop offset="0.5" stop-color="#107ad5"/>
|
||||
<stop offset="1" stop-color="#0a63c9"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M143.984,93.02a1.813,1.813,0,0,1-.07.52,1.055,1.055,0,0,1-.06.24c.01.01,0,.02-.01.03a.9.9,0,0,1-.079.21,2.1,2.1,0,0,1-.15.3,2.5,2.5,0,0,1-1.059,1.05l-4.636,2.62-.4.23L132.584,101l-1.139.64-21.5,12.16-.419.24-8.193,4.63-1.019.57-1.009.57-.51.29c-.17.09-.349.19-.529.29-.02.01-.03.02-.05.03a4.689,4.689,0,0,1-1.169.43h-.01a5.834,5.834,0,0,1-1.509.19,5.973,5.973,0,0,1-2.7-.62c-.14-.08-.28-.16-.42-.23-.05-.03-.1-.06-.16-.09l-6.414-3.63-.869-.49-.62-.35-.13-.07-.469-.27-.07-.04-.21-.12-.15-.09-.16-.09-.2-.11-.239-.13-.12-.07-.03-.02-.32-.18-.28-.16-.08-.04-.359-.2L63.416,103.8,59.6,101.64,58.46,101l-4.936-2.78L53,97.92l-4.506-2.55a2.546,2.546,0,0,1-1.059-1.05,2.233,2.233,0,0,1-.15-.31l-.21-.99a2.645,2.645,0,0,1,1.329-2.3l.03-.03c.02,0,.04-.02.06-.02L53,88.13l.529-.3L74.845,75.78,76.224,75l5.306-2.99.359-.21.08-.04.28-.16.32-.18.03-.02.12-.07.239-.13.2-.11.16-.09.15-.08.21-.12.07-.04.469-.27.13-.07.62-.35.869-.5,6.414-3.62c.2-.11.39-.22.58-.32a6.069,6.069,0,0,1,5.385,0c.2.1.389.21.579.32l10.731,6.06,6.674,3.77L137.52,87.83l.4.23,4.636,2.61c.03,0,.05.02.07.02l.02.03A2.633,2.633,0,0,1,143.984,93.02Z" fill="#123b6d"/>
|
||||
<polygon points="110 49 82 49 82 75 110 101 138 101 138 75 110 49" fill="#28a8ea"/>
|
||||
<path d="M110,101v26H82l-.5-.42-.07-.06v-.01l-.09-.08-.18-.15-.34-.29-.03-.02-.44-.38-.01-.01-.11-.09-.05-.04-.05-.05-.59-.5h-.01l-.12-.11-.08-.07-.05-.04-.12-.1-.3-.25-.6-.49L54,103.99l-1-.82V75H82l.6.56.15.14.44.41.31.28.28.26.47.44.13.12.62.58Z" fill="#0364b8"/>
|
||||
<rect x="54" y="101" width="28" height="26" fill="#14447d"/>
|
||||
<rect x="110" y="101" width="28" height="26" fill="#0078d4"/>
|
||||
<rect x="110" y="49" width="28" height="26" fill="#50d9ff"/>
|
||||
<rect x="53" y="49" width="29" height="26" fill="#0078d4"/>
|
||||
<rect x="82" y="75" width="28" height="26" fill="#0078d4"/>
|
||||
<path d="M58.38,34h74.24A5.38,5.38,0,0,1,138,39.38V49a0,0,0,0,1,0,0H53a0,0,0,0,1,0,0V39.38A5.38,5.38,0,0,1,58.38,34Z" fill="#0358a7"/>
|
||||
<path d="M138,93.829,96.942,116.108l-.192.111a1.94,1.94,0,0,1-.2.107c-.074.035-.151.066-.227.093l-1.34-.751-.167-.089c-.08-.04-.161-.082-.237-.13l-.1-.062-8.132-4.52-.664-.375-.545-.3-1.2-.675L53,92.318v32.259h85Z" fill="#0a2767" opacity="0.1"/>
|
||||
<path d="M138,94.829,96.942,117.108l-.192.111a1.94,1.94,0,0,1-.2.107c-.074.035-.151.066-.227.093l-1.34-.751-.167-.089c-.08-.04-.161-.082-.237-.13l-.1-.062-8.132-4.52-.664-.375-.545-.3-1.2-.675L53,93.318v32.259h85Z" fill="#0a2767" opacity="0.1"/>
|
||||
<path d="M138,95.829,96.942,118.108l-.192.111a1.94,1.94,0,0,1-.2.107c-.074.035-.151.066-.227.093l-1.34-.751-.167-.089c-.08-.04-.161-.082-.237-.13l-.1-.062-8.132-4.52-.664-.375-.545-.3-1.2-.675L53,94.318v32.259h85Z" fill="#0a2767" opacity="0.1"/>
|
||||
<path d="M138,96.829,96.942,119.108l-.192.111a1.94,1.94,0,0,1-.2.107c-.074.035-.151.066-.227.093l-1.34-.751-.167-.089c-.08-.04-.161-.082-.237-.13l-.1-.062-8.132-4.52-.664-.375-.545-.3-1.2-.675L53,95.318v32.259h85Z" fill="#0a2767" opacity="0.1"/>
|
||||
<path d="M142.641,95.242v0l-.054.029-.013.008-43.8,23.765a6.019,6.019,0,0,1-.587.319h0a6.331,6.331,0,0,1-5.389,0h0a6.138,6.138,0,0,1-.587-.319l2.061,14.818,43.658,13.047a6.235,6.235,0,0,0,3.564-1.1,5.686,5.686,0,0,0,2.5-4.671V93.022A2.542,2.542,0,0,1,142.641,95.242Z" fill="#1490df"/>
|
||||
<path d="M100.479,118.163l-1.629.883a6.261,6.261,0,0,1-.587.319h0a6.329,6.329,0,0,1-5.388,0h0a6.261,6.261,0,0,1-.587-.319l2.061,14.818,43.658,13.047a6.241,6.241,0,0,0,3.565-1.1,5.773,5.773,0,0,0,2.339-3.356Z" opacity="0.05"/>
|
||||
<path d="M143.578,143.412,99.439,118.727l-.589.319a6.261,6.261,0,0,1-.587.319h0a6.329,6.329,0,0,1-5.388,0h0a6.261,6.261,0,0,1-.587-.319l2.061,14.818,43.658,13.047a6.241,6.241,0,0,0,3.565-1.1A5.851,5.851,0,0,0,143.578,143.412Z" opacity="0.05"/>
|
||||
<path d="M143.088,144.284,98.406,119.3l-.143.07h0a6.329,6.329,0,0,1-5.388,0h0a6.261,6.261,0,0,1-.587-.319l2.061,14.818,43.658,13.047a6.241,6.241,0,0,0,3.565-1.1A5.943,5.943,0,0,0,143.088,144.284Z" opacity="0.05"/>
|
||||
<path d="M142.444,145.069,97.578,119.977l-.223-.119-.176-.1a6.334,6.334,0,0,1-4.305-.394h0a6.261,6.261,0,0,1-.587-.319l2.061,14.818,43.658,13.047a6.241,6.241,0,0,0,3.565-1.1A6.015,6.015,0,0,0,142.444,145.069Z" opacity="0.05"/>
|
||||
<path d="M141.63,145.76c-.13.09-.26.18-.39.26s-.27.15-.41.22c-.16.08-.32.15-.48.22-.01,0-.02.01-.03.01q-.27.1-.54.18a3.3,3.3,0,0,1-.62.15.7.7,0,0,1-.14.03c-.11.02-.22.03-.34.04a4.632,4.632,0,0,1-.65.04H53.15a6.01,6.01,0,0,1-6.08-5.94V93.02l1.38.77.02.01c.01.01.01.01.02.01L53,96.32,61.42,101,82,112.44l.6.34.15.08.44.25.31.17.28.15.47.27.13.07.62.34,7.44,4.14c.19.12.39.23.59.33l2.48,1.39,1.57.88h.01l6.67,3.73,1.79,1,1.79,1,.75.42,1.04.58Z" fill="#28a8ea"/>
|
||||
<path d="M85,63v59.62a5.337,5.337,0,0,1-.37,1.96,5.272,5.272,0,0,1-.52,1,4.839,4.839,0,0,1-.86,1,5.173,5.173,0,0,1-.51.42,5.462,5.462,0,0,1-1.03.58,5.378,5.378,0,0,1-2.09.42H47v-4h.07V93.02a2.644,2.644,0,0,1,1.33-2.3l.03-.03c.02,0,.04-.02.06-.02L53,88.13V57H79.62a4.547,4.547,0,0,1,2.38.69A6.374,6.374,0,0,1,85,63Z" opacity="0.05"/>
|
||||
<path d="M84.25,63.1v58.52a5.432,5.432,0,0,1-.86,2.96,4.746,4.746,0,0,1-.84,1,3.379,3.379,0,0,1-.55.45,3.747,3.747,0,0,1-.66.4c-.1.06-.2.1-.3.15a5.292,5.292,0,0,1-2.08.42H47v-3h.07V93.02a2.644,2.644,0,0,1,1.33-2.3l.03-.03c.02,0,.04-.02.06-.02L53,88.13V57.25H78.87A4.942,4.942,0,0,1,82,58.42,6.149,6.149,0,0,1,84.25,63.1Z" opacity="0.075"/>
|
||||
<path d="M83.5,63.19v57.43a5.45,5.45,0,0,1-1.5,3.82,1.92,1.92,0,0,1-.15.14,4.911,4.911,0,0,1-1.47,1c-.01,0-.02.01-.03.01a5.268,5.268,0,0,1-2.04.41H47v-2h.07V93.02a2.644,2.644,0,0,1,1.33-2.3l.03-.03c.02,0,.04-.02.06-.02L53,88.13V57.5H78.12A5.21,5.21,0,0,1,82,59.32,5.846,5.846,0,0,1,83.5,63.19Z" opacity="0.1"/>
|
||||
<path d="M82.75,63.28v56.34a5.735,5.735,0,0,1-.75,2.87,4.989,4.989,0,0,1-2.29,2.09c-.12.05-.25.1-.38.14a5.088,5.088,0,0,1-1.67.28H47v-1h.07V93.02a2.644,2.644,0,0,1,1.33-2.3l.03-.03c.02,0,.04-.02.06-.02L53,88.13V57.75H77.37A5.373,5.373,0,0,1,82,60.51,5.5,5.5,0,0,1,82.75,63.28Z" opacity="0.125"/>
|
||||
<path d="M82,63.38v55.24a5.158,5.158,0,0,1-3.74,5.22A4.731,4.731,0,0,1,77,124H47.07V93.02a2.644,2.644,0,0,1,1.33-2.3l.03-.03c.02,0,.04-.02.06-.02L53,88.13V58H76.62A5.382,5.382,0,0,1,82,63.38Z" opacity="0.2"/>
|
||||
<rect x="17" y="58" width="65" height="65" rx="5.38" fill="url(#a)"/>
|
||||
<path d="M35.041,81.643A14.637,14.637,0,0,1,40.785,75.3a17.366,17.366,0,0,1,9.128-2.287,16.154,16.154,0,0,1,8.444,2.169,14.489,14.489,0,0,1,5.59,6.062,19.581,19.581,0,0,1,1.958,8.916,20.653,20.653,0,0,1-2.017,9.329,14.837,14.837,0,0,1-5.755,6.274,16.788,16.788,0,0,1-8.763,2.228,16.542,16.542,0,0,1-8.632-2.193,14.705,14.705,0,0,1-5.661-6.074A19.091,19.091,0,0,1,33.1,90.913,21.187,21.187,0,0,1,35.041,81.643Zm6.121,14.895a9.5,9.5,0,0,0,3.231,4.175,8.44,8.44,0,0,0,5.048,1.521,8.862,8.862,0,0,0,5.39-1.568,9.107,9.107,0,0,0,3.137-4.187,16.181,16.181,0,0,0,1-5.826,17.723,17.723,0,0,0-.943-5.9A9.345,9.345,0,0,0,55,80.417a8.35,8.35,0,0,0-5.343-1.651A8.718,8.718,0,0,0,44.488,80.3a9.576,9.576,0,0,0-3.3,4.21,16.71,16.71,0,0,0-.024,12.029Z" fill="#fff"/>
|
||||
<rect width="180" height="180" fill="none"/>
|
||||
</svg>
|
After Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 8.7 KiB |
1
public/integrations/zapier.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64"><path d="M63.207 26.418H44.432l13.193-13.193c-1.015-1.522-2.03-2.537-3.045-4.06a29.025 29.025 0 0 1-4.059-3.552L37.33 18.807V.54a17.252 17.252 0 0 0-5.074-.507A15.629 15.629 0 0 0 27.18.54v18.775l-13.7-13.7A13.7 13.7 0 0 0 9.42 9.166c-1.015 1.522-2.537 2.537-3.552 4.06L19.06 26.418H.794l-.507 5.074a15.629 15.629 0 0 0 .507 5.074H19.57l-13.7 13.7a27.198 27.198 0 0 0 7.611 7.611l13.193-13.193V63.46a17.252 17.252 0 0 0 5.074.507 15.629 15.629 0 0 0 5.074-.507V44.686L50.014 57.88a13.7 13.7 0 0 0 4.059-3.552 29.025 29.025 0 0 0 3.552-4.059L44.432 37.074h18.775A17.252 17.252 0 0 0 63.715 32a19.028 19.028 0 0 0-.507-5.582zm-23.342 5.074a25.726 25.726 0 0 1-1.015 6.597 15.223 15.223 0 0 1-6.597 1.015 25.726 25.726 0 0 1-6.597-1.015 15.223 15.223 0 0 1-1.015-6.597 25.726 25.726 0 0 1 1.015-6.597 15.223 15.223 0 0 1 6.597-1.015 25.726 25.726 0 0 1 6.597 1.015 29.684 29.684 0 0 1 1.015 6.597z" fill="#ff4a00"/></svg>
|
After Width: | Height: | Size: 981 B |
Before Width: | Height: | Size: 8.8 KiB |
1
public/integrations/zoom.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg height="64" viewBox="0 0 32 32" width="64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path d="m-200-175h1000v562h-1000z"/></clipPath><clipPath id="b"><circle cx="107" cy="106" r="102"/></clipPath><clipPath id="c"><circle cx="107" cy="106" r="100"/></clipPath><clipPath id="d"><circle cx="107" cy="106" r="92"/></clipPath><clipPath id="e"><path clipRule="evenodd" d="m135 94.06 26-19c2.27-1.85 4-1.42 4 2v57.94c0 3.84-2.16 3.4-4 2l-26-19zm-88-16.86v43.2a17.69 17.69 0 0 0 17.77 17.6h63a3.22 3.22 0 0 0 3.23-3.2v-43.2a17.69 17.69 0 0 0 -17.77-17.6h-63a3.22 3.22 0 0 0 -3.23 3.2z"/></clipPath><g clip-path="url(#a)" transform="translate(0 -178)"><path d="m232 61h366v90h-366z" fill="#4a8cff"/></g><g clip-path="url(#a)" transform="matrix(.156863 0 0 .156863 -.784314 -.627496)"><g clip-path="url(#b)"><path d="m0-1h214v214h-214z" fill="#e5e5e4"/></g><g clip-path="url(#c)"><path d="m2 1h210v210h-210z" fill="#fff"/></g><g clip-path="url(#d)"><path d="m10 9h194v194h-194z" fill="#4a8cff"/></g><g clip-path="url(#e)"><path d="m42 69h128v74h-128z" fill="#fff"/></g></g><g clip-path="url(#a)" transform="translate(0 -178)"><path d="m232 19.25h180v38.17h-180z" fill="#90908f"/></g></svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -7,77 +7,77 @@
|
|||
@layer components {
|
||||
/* Primary buttons */
|
||||
.btn-xs.btn-primary {
|
||||
@apply inline-flex items-center px-2.5 py-1.5 border border-transparent text-xs font-medium rounded shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500;
|
||||
@apply inline-flex items-center px-2.5 py-1.5 border border-transparent text-xs font-medium rounded shadow-sm text-white bg-neutral-900 hover:bg-neutral-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-500;
|
||||
}
|
||||
|
||||
.btn-sm.btn-primary {
|
||||
@apply inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500;
|
||||
@apply inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-sm shadow-sm text-white bg-neutral-900 hover:bg-neutral-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-500;
|
||||
}
|
||||
|
||||
.btn.btn-primary {
|
||||
@apply inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500;
|
||||
@apply inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-sm shadow-sm text-white bg-neutral-900 hover:bg-neutral-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-500;
|
||||
}
|
||||
|
||||
.btn-lg.btn-primary {
|
||||
@apply inline-flex items-center px-4 py-2 border border-transparent text-base font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500;
|
||||
@apply inline-flex items-center px-4 py-2 border border-transparent text-base font-medium rounded-sm shadow-sm text-white bg-neutral-900 hover:bg-neutral-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-500;
|
||||
}
|
||||
|
||||
.btn-xl.btn-primary {
|
||||
@apply inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500;
|
||||
@apply inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-sm shadow-sm text-white bg-neutral-900 hover:bg-neutral-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-500;
|
||||
}
|
||||
|
||||
.btn-wide.btn-primary {
|
||||
@apply w-full text-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500;
|
||||
@apply w-full text-center px-4 py-2 border border-transparent text-sm font-medium rounded-sm shadow-sm text-white bg-neutral-900 hover:bg-neutral-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-500;
|
||||
}
|
||||
|
||||
/* Secondary buttons */
|
||||
.btn-xs.btn-secondary {
|
||||
@apply inline-flex items-center px-2.5 py-1.5 border border-transparent text-xs font-medium rounded text-blue-700 bg-blue-100 hover:bg-blue-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500;
|
||||
@apply inline-flex items-center px-2.5 py-1.5 border border-transparent text-xs font-medium rounded text-neutral-700 bg-neutral-100 hover:bg-neutral-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-500;
|
||||
}
|
||||
|
||||
.btn-sm.btn-secondary {
|
||||
@apply inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-blue-700 bg-blue-100 hover:bg-blue-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500;
|
||||
@apply inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-sm text-neutral-700 bg-neutral-100 hover:bg-neutral-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-500;
|
||||
}
|
||||
|
||||
.btn.btn-secondary {
|
||||
@apply inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-blue-700 bg-blue-100 hover:bg-blue-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500;
|
||||
@apply inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-sm text-neutral-700 bg-neutral-100 hover:bg-neutral-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-500;
|
||||
}
|
||||
|
||||
.btn-lg.btn-secondary {
|
||||
@apply inline-flex items-center px-4 py-2 border border-transparent text-base font-medium rounded-md text-blue-700 bg-blue-100 hover:bg-blue-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500;
|
||||
@apply inline-flex items-center px-4 py-2 border border-transparent text-base font-medium rounded-sm text-neutral-700 bg-neutral-100 hover:bg-neutral-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-500;
|
||||
}
|
||||
|
||||
.btn-xl.btn-secondary {
|
||||
@apply inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md text-blue-700 bg-blue-100 hover:bg-blue-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500;
|
||||
@apply inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-sm text-neutral-700 bg-neutral-100 hover:bg-neutral-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-500;
|
||||
}
|
||||
|
||||
.btn-wide.btn-secondary {
|
||||
@apply w-full text-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-blue-700 bg-blue-100 hover:bg-blue-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500;
|
||||
@apply w-full text-center px-4 py-2 border border-transparent text-sm font-medium rounded-sm text-neutral-700 bg-neutral-100 hover:bg-neutral-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-500;
|
||||
}
|
||||
|
||||
/* White buttons */
|
||||
.btn-xs.btn-white {
|
||||
@apply inline-flex items-center px-2.5 py-1.5 border border-gray-300 shadow-sm text-xs font-medium rounded text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500;
|
||||
@apply inline-flex items-center px-2.5 py-1.5 border border-gray-300 shadow-sm text-xs font-medium rounded text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-500;
|
||||
}
|
||||
|
||||
.btn-sm.btn-white {
|
||||
@apply inline-flex items-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500;
|
||||
@apply inline-flex items-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-sm text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-500;
|
||||
}
|
||||
|
||||
.btn.btn-white {
|
||||
@apply inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500;
|
||||
@apply inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-sm text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-500;
|
||||
}
|
||||
|
||||
.btn-lg.btn-white {
|
||||
@apply inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-base font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500;
|
||||
@apply inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-base font-medium rounded-sm text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-500;
|
||||
}
|
||||
|
||||
.btn-xl.btn-white {
|
||||
@apply inline-flex items-center px-6 py-3 border border-gray-300 shadow-sm text-base font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500;
|
||||
@apply inline-flex items-center px-6 py-3 border border-gray-300 shadow-sm text-base font-medium rounded-sm text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-500;
|
||||
}
|
||||
|
||||
.btn-wide.btn-white {
|
||||
@apply w-full text-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500;
|
||||
@apply w-full text-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-sm text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-500;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,19 +85,119 @@
|
|||
--tw-ring-shadow: 0 0 0 0 0;
|
||||
}
|
||||
|
||||
.loader {
|
||||
margin: 80px auto;
|
||||
border: 8px solid #f3f3f3; /* Light grey */
|
||||
border-top: 8px solid #039be5; /* Blue */
|
||||
border-radius: 50%;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
animation: spin 2s linear infinite;
|
||||
/* !important to override react-select */
|
||||
.react-select__value-container{
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
.react-select__input input {
|
||||
border: 0 !important; box-shadow: none !important;
|
||||
}
|
||||
|
||||
.react-select__option--is-focused{
|
||||
background: #000 !important;
|
||||
color: #fff !important
|
||||
}
|
||||
|
||||
.react-select__control{
|
||||
border: 2px solid transparent !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.react-select__control.react-select__control--is-focused{
|
||||
border: 2px solid #000 !important;
|
||||
border-color: #000 !important;
|
||||
box-shadow: none !important;
|
||||
margin: -1px;
|
||||
padding: 1px;
|
||||
|
||||
}
|
||||
|
||||
/* !important to override react-dates */
|
||||
.DateRangePickerInput__withBorder{
|
||||
border: 0 !important
|
||||
}
|
||||
.DateInput_input{
|
||||
border: 1px solid #D1D5DB !important;
|
||||
border-radius: 2px !important;
|
||||
font-size: inherit !important;
|
||||
font-weight: inherit !important;
|
||||
color: #000;
|
||||
padding: 11px 11px 9px !important;
|
||||
line-height: 16px !important;
|
||||
}
|
||||
|
||||
.DateInput_input__focused{
|
||||
border: 2px solid #000 !important;
|
||||
border-radius: 2px !important;
|
||||
box-shadow: none !important;
|
||||
padding: 10px 10px 9px !important;
|
||||
}
|
||||
|
||||
.DateRangePickerInput_arrow{
|
||||
padding: 0px 10px;
|
||||
}
|
||||
|
||||
.loader {
|
||||
display: block;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin: 60px auto;
|
||||
position: relative;
|
||||
border: 4px solid #000;
|
||||
animation: loader 2s infinite ease;
|
||||
}
|
||||
|
||||
.loader-inner {
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
background-color: #000;
|
||||
animation: loader-inner 2s infinite ease-in;
|
||||
}
|
||||
|
||||
@keyframes loader {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes loader-inner {
|
||||
0% {
|
||||
height: 0%;
|
||||
}
|
||||
|
||||
25% {
|
||||
height: 0%;
|
||||
}
|
||||
|
||||
50% {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
75% {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
100% {
|
||||
height: 0%;
|
||||
}
|
||||
}
|
||||
|
||||
nav#nav--settings > a {
|
||||
|
@ -109,11 +209,11 @@ nav#nav--settings > a svg {
|
|||
}
|
||||
|
||||
nav#nav--settings > a.active {
|
||||
@apply bg-blue-50 border-blue-500 text-blue-700 hover:bg-blue-50 hover:text-blue-700;
|
||||
@apply bg-neutral-50 border-neutral-500 text-neutral-700 hover:bg-neutral-50 hover:text-neutral-700;
|
||||
}
|
||||
|
||||
nav#nav--settings > a.active svg {
|
||||
@apply text-blue-500;
|
||||
@apply text-neutral-500;
|
||||
}
|
||||
|
||||
|
||||
|
@ -141,22 +241,7 @@ body {
|
|||
box-shadow: none;
|
||||
}
|
||||
|
||||
.weekdaySelect {
|
||||
font-family: "Courier New", sans-serif;
|
||||
}
|
||||
|
||||
.weekdaySelect button.active:first-child {
|
||||
margin-left: -1px !important;
|
||||
}
|
||||
|
||||
.weekdaySelect button:not(.active) {
|
||||
padding-left: calc(0.5rem + 0px);
|
||||
margin-right: 1px;
|
||||
}
|
||||
|
||||
.weekdaySelect button.active + button.active {
|
||||
border-color: rgba(3, 169, 244, var(--tw-border-opacity))
|
||||
rgba(3, 169, 244, var(--tw-border-opacity))
|
||||
rgba(3, 169, 244, var(--tw-border-opacity))
|
||||
white;
|
||||
.weekdaySelect button.active {
|
||||
background: #000;
|
||||
color: #fff;
|
||||
}
|
|
@ -5,28 +5,82 @@ module.exports = {
|
|||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
gray: {
|
||||
100: "#EBF1F5",
|
||||
200: "#D9E3EA",
|
||||
300: "#C5D2DC",
|
||||
400: "#9BA9B4",
|
||||
500: "#707D86",
|
||||
600: "#55595F",
|
||||
700: "#33363A",
|
||||
800: "#25282C",
|
||||
900: "#151719",
|
||||
neutral: {
|
||||
50: "#F7F8F9",
|
||||
100: "#F4F5F6",
|
||||
200: "#EAEEF2",
|
||||
300: "#C6CCD5",
|
||||
400: "#9BA6B6",
|
||||
500: "#708097",
|
||||
600: "#657388",
|
||||
700: "#373F4A",
|
||||
800: "#1F2937",
|
||||
900: "#1A1A1A",
|
||||
},
|
||||
blue: {
|
||||
100: "#b3e5fc",
|
||||
200: "#81d4fa",
|
||||
300: "#4fc3f7",
|
||||
400: "#29b6f6",
|
||||
500: "#03a9f4",
|
||||
600: "#039be5",
|
||||
700: "#0288d1",
|
||||
800: "#0277bd",
|
||||
900: "#01579b",
|
||||
primary: {
|
||||
50: "#F4F4F4",
|
||||
100: "#E8E8E8",
|
||||
200: "#C6C6C6",
|
||||
300: "#A3A3A3",
|
||||
400: "#5F5F5F",
|
||||
500: "#1A1A1A",
|
||||
600: "#171717",
|
||||
700: "#141414",
|
||||
800: "#101010",
|
||||
900: "#0D0D0D",
|
||||
},
|
||||
secondary: {
|
||||
50: "#F5F8F7",
|
||||
100: "#EBF0F0",
|
||||
200: "#CDDAD9",
|
||||
300: "#AEC4C2",
|
||||
400: "#729894",
|
||||
500: "#356C66",
|
||||
600: "#30615C",
|
||||
700: "#28514D",
|
||||
800: "#20413D",
|
||||
900: "#223B41",
|
||||
},
|
||||
red: {
|
||||
50: "#FEF2F2",
|
||||
100: "#FEE2E2",
|
||||
200: "#FECACA",
|
||||
300: "#FCA5A5",
|
||||
400: "#F87171",
|
||||
500: "#EF4444",
|
||||
600: "#DC2626",
|
||||
700: "#B91C1C",
|
||||
800: "#991B1B",
|
||||
900: "#7F1D1D",
|
||||
},
|
||||
orange: {
|
||||
50: "#FFF7ED",
|
||||
100: "#FFEDD5",
|
||||
200: "#FED7AA",
|
||||
300: "#FDBA74",
|
||||
400: "#FB923C",
|
||||
500: "#F97316",
|
||||
600: "#EA580C",
|
||||
700: "#C2410C",
|
||||
800: "#9A3412",
|
||||
900: "#7C2D12",
|
||||
},
|
||||
green: {
|
||||
50: "#ECFDF5",
|
||||
100: "#D1FAE5",
|
||||
200: "#A7F3D0",
|
||||
300: "#6EE7B7",
|
||||
400: "#34D399",
|
||||
500: "#10B981",
|
||||
600: "#059669",
|
||||
700: "#047857",
|
||||
800: "#065F46",
|
||||
900: "#064E3B",
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
inter: ["Inter", "sans-serif"],
|
||||
kollektif: ["Kollektif", "sans-serif"],
|
||||
},
|
||||
maxHeight: (theme) => ({
|
||||
0: "0",
|
||||
|
|