Merge branch 'main' of github.com:calendso/calendso

This commit is contained in:
Peer Richelsen 2021-08-03 10:12:17 +02:00
commit 0bc0119362
8 changed files with 189 additions and 48 deletions

View file

@ -171,10 +171,13 @@ const DatePicker = ({
<span className="w-1/2 text-gray-600 dark:text-white"> <span className="w-1/2 text-gray-600 dark:text-white">
{dayjs().month(selectedMonth).format("MMMM YYYY")} {dayjs().month(selectedMonth).format("MMMM YYYY")}
</span> </span>
<div className="w-1/2 text-right"> <div className="w-1/2 text-right text-gray-600 dark:text-gray-400">
<button <button
onClick={decrementMonth} onClick={decrementMonth}
className={"mr-4 " + (selectedMonth <= dayjs().tz(inviteeTimeZone).month() && "text-gray-400")} className={
"mr-4 " +
(selectedMonth <= dayjs().tz(inviteeTimeZone).month() && "text-gray-400 dark:text-gray-600")
}
disabled={selectedMonth <= dayjs().tz(inviteeTimeZone).month()}> disabled={selectedMonth <= dayjs().tz(inviteeTimeZone).month()}>
<ChevronLeftIcon className="w-5 h-5" /> <ChevronLeftIcon className="w-5 h-5" />
</button> </button>

View file

@ -17,6 +17,7 @@
"@heroicons/react": "^1.0.1", "@heroicons/react": "^1.0.1",
"@jitsu/sdk-js": "^2.0.1", "@jitsu/sdk-js": "^2.0.1",
"@prisma/client": "^2.23.0", "@prisma/client": "^2.23.0",
"@radix-ui/react-collapsible": "^0.0.16",
"@tailwindcss/forms": "^0.2.1", "@tailwindcss/forms": "^0.2.1",
"async": "^3.2.0", "async": "^3.2.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",

View file

@ -4,6 +4,7 @@ import Head from "next/head";
import { ChevronDownIcon, ClockIcon, GlobeIcon } from "@heroicons/react/solid"; import { ChevronDownIcon, ClockIcon, GlobeIcon } from "@heroicons/react/solid";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import dayjs, { Dayjs } from "dayjs"; import dayjs, { Dayjs } from "dayjs";
import * as Collapsible from "@radix-ui/react-collapsible";
import prisma, { whereAndSelect } from "@lib/prisma"; import prisma, { whereAndSelect } from "@lib/prisma";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "../../lib/telemetry"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "../../lib/telemetry";
@ -139,19 +140,21 @@ export default function Type(props): Type {
<ClockIcon className="inline-block w-4 h-4 mr-1 -mt-1" /> <ClockIcon className="inline-block w-4 h-4 mr-1 -mt-1" />
{props.eventType.length} minutes {props.eventType.length} minutes
</p> </p>
<button
onClick={() => setIsTimeOptionsOpen(!isTimeOptionsOpen)} <Collapsible.Root open={isTimeOptionsOpen} onOpenChange={setIsTimeOptionsOpen}>
className="text-gray-500 mb-1 px-2 py-1 -ml-2"> <Collapsible.Trigger className="text-gray-500 mb-1 px-2 py-1 -ml-2">
<GlobeIcon className="inline-block w-4 h-4 mr-1 -mt-1" /> <GlobeIcon className="inline-block w-4 h-4 mr-1 -mt-1" />
{timeZone()} {timeZone()}
<ChevronDownIcon className="inline-block w-4 h-4 ml-1 -mt-1" /> <ChevronDownIcon className="inline-block w-4 h-4 ml-1 -mt-1" />
</button> </Collapsible.Trigger>
{isTimeOptionsOpen && ( <Collapsible.Content>
<TimeOptions <TimeOptions
onSelectTimeZone={handleSelectTimeZone} onSelectTimeZone={handleSelectTimeZone}
onToggle24hClock={handleToggle24hClock} onToggle24hClock={handleToggle24hClock}
/> />
)} </Collapsible.Content>
</Collapsible.Root>
<p className="dark:text-gray-200 text-gray-600 mt-3 mb-8">{props.eventType.description}</p> <p className="dark:text-gray-200 text-gray-600 mt-3 mb-8">{props.eventType.description}</p>
</div> </div>
<DatePicker <DatePicker

View file

@ -218,7 +218,9 @@ export default function Book(props: any): JSX.Element {
</div> </div>
{locations.length > 1 && ( {locations.length > 1 && (
<div className="mb-4"> <div className="mb-4">
<span className="block text-sm font-medium text-gray-700">Location</span> <span className="block text-sm font-medium dark:text-white text-gray-700">
Location
</span>
{locations.map((location) => ( {locations.map((location) => (
<label key={location.type} className="block"> <label key={location.type} className="block">
<input <input
@ -230,7 +232,9 @@ export default function Book(props: any): JSX.Element {
value={location.type} value={location.type}
checked={selectedLocation === location.type} checked={selectedLocation === location.type}
/> />
<span className="text-sm ml-2">{locationLabels[location.type]}</span> <span className="text-sm ml-2 dark:text-gray-500">
{locationLabels[location.type]}
</span>
</label> </label>
))} ))}
</div> </div>

View file

@ -3,6 +3,8 @@ import prisma from "../../lib/prisma";
import { getSession, useSession } from "next-auth/client"; import { getSession, useSession } from "next-auth/client";
import Shell from "../../components/Shell"; import Shell from "../../components/Shell";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import dayjs from "dayjs";
export default function Bookings({ bookings }) { export default function Bookings({ bookings }) {
const [, loading] = useSession(); const [, loading] = useSession();
@ -37,6 +39,28 @@ export default function Bookings({ bookings }) {
<div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"> <div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
<div className="shadow overflow-hidden border-b border-gray-200 sm:rounded-sm"> <div className="shadow overflow-hidden border-b border-gray-200 sm:rounded-sm">
<table className="min-w-full divide-y divide-gray-200"> <table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Person
</th>
<th
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Event
</th>
<th
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Date
</th>
<th scope="col" className="relative px-6 py-3">
<span className="sr-only">Actions</span>
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200"> <tbody className="bg-white divide-y divide-gray-200">
{bookings {bookings
.filter((booking) => !booking.confirmed && !booking.rejected) .filter((booking) => !booking.confirmed && !booking.rejected)
@ -70,11 +94,14 @@ export default function Bookings({ bookings }) {
You and {booking.attendees[0].name} You and {booking.attendees[0].name}
</div> </div>
</td> </td>
{/* <td className="px-6 py-4 whitespace-nowrap"> <td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm text-gray-500"> <div className="text-sm text-gray-900">
{dayjs(booking.startTime).format("D MMMM YYYY HH:mm")} {dayjs(booking.startTime).format("D MMMM YYYY")}
</div> </div>
</td> */} <div className="text-sm text-gray-500">
{dayjs(booking.startTime).format("HH:mm")} - {dayjs(booking.endTime).format("HH:mm")}
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> <td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
{!booking.confirmed && !booking.rejected && ( {!booking.confirmed && !booking.rejected && (
<> <>
@ -137,7 +164,7 @@ export async function getServerSideProps(context) {
}, },
}); });
const bookings = await prisma.booking.findMany({ const b = await prisma.booking.findMany({
where: { where: {
userId: user.id, userId: user.id,
}, },
@ -149,11 +176,17 @@ export async function getServerSideProps(context) {
confirmed: true, confirmed: true,
rejected: true, rejected: true,
id: true, id: true,
startTime: true,
endTime: true,
}, },
orderBy: { orderBy: {
startTime: "desc", startTime: "asc",
}, },
}); });
const bookings = b.map(booking=>{
return ({...booking, startTime:booking.startTime.toISOString(), endTime:booking.endTime.toISOString(),})
});
return { props: { bookings } }; return { props: { bookings } };
} }

View file

@ -20,7 +20,7 @@ import {
PlusIcon, PlusIcon,
UserIcon, UserIcon,
} from "@heroicons/react/solid"; } from "@heroicons/react/solid";
import Loader from '@components/Loader'; import Loader from "@components/Loader";
export default function Availability({ user, types }) { export default function Availability({ user, types }) {
const [session, loading] = useSession(); const [session, loading] = useSession();
@ -42,7 +42,7 @@ export default function Availability({ user, types }) {
// TODO: Add validation // TODO: Add validation
const response = await fetch("/api/availability/eventtype", { await fetch("/api/availability/eventtype", {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
title: enteredTitle, title: enteredTitle,
@ -66,7 +66,7 @@ export default function Availability({ user, types }) {
} }
if (loading) { if (loading) {
return <Loader/>; return <Loader />;
} }
return ( return (
@ -79,12 +79,14 @@ export default function Availability({ user, types }) {
heading="Event Types" heading="Event Types"
subtitle="Create events to share for people to book on your calendar." subtitle="Create events to share for people to book on your calendar."
CTA={ CTA={
<button types.length !== 0 && (
onClick={toggleAddModal} <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"> onClick={toggleAddModal}
<PlusIcon className="w-5 h-5 mr-1" /> 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">
New event type <PlusIcon className="w-5 h-5 mr-1" />
</button> New event type
</button>
)
}> }>
<div className="bg-white shadow overflow-hidden sm:rounded-sm"> <div className="bg-white shadow overflow-hidden sm:rounded-sm">
<ul className="divide-y divide-neutral-200"> <ul className="divide-y divide-neutral-200">
@ -261,9 +263,9 @@ export default function Availability({ user, types }) {
</ul> </ul>
</div> </div>
{types.length === 0 && ( {types.length === 0 && (
<div className="text-center max-w-lg mx-auto"> <div className="md:py-20">
<svg <svg
className="mx-auto mb-4 w-32 h-32" className="w-1/2 md:w-32 mx-auto block mb-4"
viewBox="0 0 132 132" viewBox="0 0 132 132"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg"> xmlns="http://www.w3.org/2000/svg">
@ -500,12 +502,19 @@ export default function Availability({ user, types }) {
</clipPath> </clipPath>
</defs> </defs>
</svg> </svg>
<div className="text-center block md:max-w-screen-sm mx-auto">
<h3 className="mt-2 text-xl font-bold text-neutral-900">Create your first event type</h3> <h3 className="mt-2 text-xl font-bold text-neutral-900">Create your first event type</h3>
<p className="mt-1 text-md text-neutral-600"> <p className="mt-1 text-md text-neutral-600">
Event types enable you to share links that show available times on your calendar and allow Event types enable you to share links that show available times on your calendar and allow
people to make bookings with you. people to make bookings with you.
</p> </p>
<button
onClick={toggleAddModal}
className="py-2 px-4 mt-6 border border-transparent rounded-sm shadow-sm text-sm font-medium text-white bg-neutral-900 hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-900">
<PlusIcon className="w-5 h-5 mr-1 inline" />
New event type
</button>
</div>
</div> </div>
)} )}
{showAddModal && ( {showAddModal && (

View file

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

View file

@ -753,6 +753,93 @@
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-2.26.0-23.9b816b3aa13cc270074f172f30d6eda8a8ce867d.tgz#cfdacfad3acc0f3bf1d7710aa8f3852fd85ac6d9" resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-2.26.0-23.9b816b3aa13cc270074f172f30d6eda8a8ce867d.tgz#cfdacfad3acc0f3bf1d7710aa8f3852fd85ac6d9"
integrity sha512-a0jIhLvw9rFh6nZTr5Y3uzP28I2xNDu3pqxANvwMNnmIoYr1wYEcO1pMXn/36BGXldDdAWMmAbhfloHA3IB8DA== integrity sha512-a0jIhLvw9rFh6nZTr5Y3uzP28I2xNDu3pqxANvwMNnmIoYr1wYEcO1pMXn/36BGXldDdAWMmAbhfloHA3IB8DA==
"@radix-ui/primitive@0.0.5":
version "0.0.5"
resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-0.0.5.tgz#8464fb4db04401bde72d36e27e05714080668d40"
integrity sha512-VeL6A5LpKYRJhDDj5tCTnzP3zm+FnvybsAkgBHQ4LUPPBnqRdWLoyKpZhlwFze/z22QHINaTIcE9Z/fTcrUR1g==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-collapsible@^0.0.16":
version "0.0.16"
resolved "https://registry.yarnpkg.com/@radix-ui/react-collapsible/-/react-collapsible-0.0.16.tgz#6a99068f70bb85a60f8cbd43f093bd3053ab61cc"
integrity sha512-kY9wojEbrpTge6sz3BZl1oXer2Szhi+MW60TSDf14mL6l8+e4ugp5y2ItGDjcW5B7AzL00dsMtqlxuAkhFjWxQ==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "0.0.5"
"@radix-ui/react-compose-refs" "0.0.5"
"@radix-ui/react-context" "0.0.5"
"@radix-ui/react-id" "0.0.6"
"@radix-ui/react-polymorphic" "0.0.12"
"@radix-ui/react-presence" "0.0.14"
"@radix-ui/react-primitive" "0.0.14"
"@radix-ui/react-use-controllable-state" "0.0.6"
"@radix-ui/react-use-layout-effect" "0.0.5"
"@radix-ui/react-compose-refs@0.0.5":
version "0.0.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-0.0.5.tgz#0f71f0de1dec341f30cebd420b6bc3d12a3037dd"
integrity sha512-O9mH9X/2EwuAEEoZXrU4alcrRbAhhZHGpIJ5bOH6rmRcokhaoWRBY1tOEe2lgHdb/bkKrY+viLi4Zq8Ju6/09Q==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-context@0.0.5":
version "0.0.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-0.0.5.tgz#7c15f46795d7765dabfaf6f9c53791ad28c521c2"
integrity sha512-bwrzAc0qc2EPepSTLBT4+93uCiI9wP78VSmPg2K+k71O/vpx7dPs0VqrewwCBNCHT54NIwaRr2hEsm2uqYi02A==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-id@0.0.6":
version "0.0.6"
resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-0.0.6.tgz#c4b27d11861805e91ac296e7758ab47e3947b65c"
integrity sha512-PzmraF34fYggsYvTIZVJ5S68WMp3aKUN3HkSmGnz4zn9zpRjkAbbg7Xn3ueQI3FQsLWKgyUfnpsmWFDndpcqYg==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-polymorphic@0.0.12":
version "0.0.12"
resolved "https://registry.yarnpkg.com/@radix-ui/react-polymorphic/-/react-polymorphic-0.0.12.tgz#bf4ae516669b68e059549538104d97322f7c876b"
integrity sha512-/GYNMicBnGzjD1d2fCAuzql1VeFrp8mqM3xfzT1kxhnV85TKdURO45jBfMgqo17XNXoNhWIAProUsCO4qFAAIg==
"@radix-ui/react-presence@0.0.14":
version "0.0.14"
resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-0.0.14.tgz#6a86058bbbf46234dd8840dacd620b3ac5797025"
integrity sha512-ufof9B76DHXV0sC8H7Lswh2AepdJFG8qEtF32JWrbA9N1bl2Jnf9px76KsagyC0MA8crGEZO5A96wizGuSgGWQ==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-compose-refs" "0.0.5"
"@radix-ui/react-primitive@0.0.14":
version "0.0.14"
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-0.0.14.tgz#752a967cb05d4c5643634fe20274e7dc905d1cce"
integrity sha512-FYOWGCrxFpLdB534aWTwMK4Pjg8cxFb+745qWhPfI+cYi+aYUddJQD3ilRHHXxCBD72ve7/PufqeB7Y/QlKqgg==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-polymorphic" "0.0.12"
"@radix-ui/react-use-callback-ref@0.0.5":
version "0.0.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-0.0.5.tgz#fa8db050229cda573dfeeae213d74ef06f6130db"
integrity sha512-z1AI221vmq9f3vsDyrCsGLCatKagbM1YeCGdRMhMsUBzFFCaJ+Axyoe/ndVqW8wwsraGWr1zYVAhIEdlC0GvPg==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-controllable-state@0.0.6":
version "0.0.6"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-0.0.6.tgz#c4b16bc911a25889333388a684a04df937e5fec7"
integrity sha512-fBk4hUSKc4N7X/NAaifWYfKKfNuOB9xvj0MBQQYS5oOTNRgg4y8/Ax3jZ0adsplXDm7ix75sxqWm0nrvUoAjcw==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-callback-ref" "0.0.5"
"@radix-ui/react-use-layout-effect@0.0.5":
version "0.0.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-0.0.5.tgz#cbbd059090edc765749da00d9f562a9abd43cbac"
integrity sha512-bNPW2JNOr/p2hXr0hfKKqrEy5deNSRF17sw3l9Z7qlEnvIbBtQ7iwY/wrxIz5P7XFyYGoXodIUDH5G8PEucE3A==
dependencies:
"@babel/runtime" "^7.13.10"
"@sinonjs/commons@^1.7.0": "@sinonjs/commons@^1.7.0":
version "1.8.3" version "1.8.3"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d"