Merge branch 'main' of github.com:calendso/calendso
This commit is contained in:
commit
0bc0119362
8 changed files with 189 additions and 48 deletions
|
@ -171,10 +171,13 @@ const DatePicker = ({
|
|||
<span className="w-1/2 text-gray-600 dark:text-white">
|
||||
{dayjs().month(selectedMonth).format("MMMM YYYY")}
|
||||
</span>
|
||||
<div className="w-1/2 text-right">
|
||||
<div className="w-1/2 text-right text-gray-600 dark:text-gray-400">
|
||||
<button
|
||||
onClick={decrementMonth}
|
||||
className={"mr-4 " + (selectedMonth <= dayjs().tz(inviteeTimeZone).month() && "text-gray-400")}
|
||||
className={
|
||||
"mr-4 " +
|
||||
(selectedMonth <= dayjs().tz(inviteeTimeZone).month() && "text-gray-400 dark:text-gray-600")
|
||||
}
|
||||
disabled={selectedMonth <= dayjs().tz(inviteeTimeZone).month()}>
|
||||
<ChevronLeftIcon className="w-5 h-5" />
|
||||
</button>
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
"@heroicons/react": "^1.0.1",
|
||||
"@jitsu/sdk-js": "^2.0.1",
|
||||
"@prisma/client": "^2.23.0",
|
||||
"@radix-ui/react-collapsible": "^0.0.16",
|
||||
"@tailwindcss/forms": "^0.2.1",
|
||||
"async": "^3.2.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
|
|
|
@ -4,6 +4,7 @@ import Head from "next/head";
|
|||
import { ChevronDownIcon, ClockIcon, GlobeIcon } from "@heroicons/react/solid";
|
||||
import { useRouter } from "next/router";
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||
|
||||
import prisma, { whereAndSelect } from "@lib/prisma";
|
||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "../../lib/telemetry";
|
||||
|
@ -139,19 +140,21 @@ export default function Type(props): Type {
|
|||
<ClockIcon className="inline-block w-4 h-4 mr-1 -mt-1" />
|
||||
{props.eventType.length} minutes
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setIsTimeOptionsOpen(!isTimeOptionsOpen)}
|
||||
className="text-gray-500 mb-1 px-2 py-1 -ml-2">
|
||||
<GlobeIcon className="inline-block w-4 h-4 mr-1 -mt-1" />
|
||||
{timeZone()}
|
||||
<ChevronDownIcon className="inline-block w-4 h-4 ml-1 -mt-1" />
|
||||
</button>
|
||||
{isTimeOptionsOpen && (
|
||||
<TimeOptions
|
||||
onSelectTimeZone={handleSelectTimeZone}
|
||||
onToggle24hClock={handleToggle24hClock}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Collapsible.Root open={isTimeOptionsOpen} onOpenChange={setIsTimeOptionsOpen}>
|
||||
<Collapsible.Trigger className="text-gray-500 mb-1 px-2 py-1 -ml-2">
|
||||
<GlobeIcon className="inline-block w-4 h-4 mr-1 -mt-1" />
|
||||
{timeZone()}
|
||||
<ChevronDownIcon className="inline-block w-4 h-4 ml-1 -mt-1" />
|
||||
</Collapsible.Trigger>
|
||||
<Collapsible.Content>
|
||||
<TimeOptions
|
||||
onSelectTimeZone={handleSelectTimeZone}
|
||||
onToggle24hClock={handleToggle24hClock}
|
||||
/>
|
||||
</Collapsible.Content>
|
||||
</Collapsible.Root>
|
||||
|
||||
<p className="dark:text-gray-200 text-gray-600 mt-3 mb-8">{props.eventType.description}</p>
|
||||
</div>
|
||||
<DatePicker
|
||||
|
|
|
@ -218,7 +218,9 @@ export default function Book(props: any): JSX.Element {
|
|||
</div>
|
||||
{locations.length > 1 && (
|
||||
<div className="mb-4">
|
||||
<span className="block text-sm font-medium text-gray-700">Location</span>
|
||||
<span className="block text-sm font-medium dark:text-white text-gray-700">
|
||||
Location
|
||||
</span>
|
||||
{locations.map((location) => (
|
||||
<label key={location.type} className="block">
|
||||
<input
|
||||
|
@ -230,7 +232,9 @@ export default function Book(props: any): JSX.Element {
|
|||
value={location.type}
|
||||
checked={selectedLocation === location.type}
|
||||
/>
|
||||
<span className="text-sm ml-2">{locationLabels[location.type]}</span>
|
||||
<span className="text-sm ml-2 dark:text-gray-500">
|
||||
{locationLabels[location.type]}
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
@ -3,6 +3,8 @@ import prisma from "../../lib/prisma";
|
|||
import { getSession, useSession } from "next-auth/client";
|
||||
import Shell from "../../components/Shell";
|
||||
import { useRouter } from "next/router";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
|
||||
export default function Bookings({ bookings }) {
|
||||
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="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>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Person
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Event
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Date
|
||||
</th>
|
||||
<th scope="col" className="relative px-6 py-3">
|
||||
<span className="sr-only">Actions</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{bookings
|
||||
.filter((booking) => !booking.confirmed && !booking.rejected)
|
||||
|
@ -70,11 +94,14 @@ export default function Bookings({ bookings }) {
|
|||
You and {booking.attendees[0].name}
|
||||
</div>
|
||||
</td>
|
||||
{/* <td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="text-sm text-gray-500">
|
||||
{dayjs(booking.startTime).format("D MMMM YYYY HH:mm")}
|
||||
</div>
|
||||
</td> */}
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="text-sm text-gray-900">
|
||||
{dayjs(booking.startTime).format("D MMMM YYYY")}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{dayjs(booking.startTime).format("HH:mm")} - {dayjs(booking.endTime).format("HH:mm")}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
{!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: {
|
||||
userId: user.id,
|
||||
},
|
||||
|
@ -149,11 +176,17 @@ export async function getServerSideProps(context) {
|
|||
confirmed: true,
|
||||
rejected: true,
|
||||
id: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
},
|
||||
orderBy: {
|
||||
startTime: "desc",
|
||||
startTime: "asc",
|
||||
},
|
||||
});
|
||||
|
||||
const bookings = b.map(booking=>{
|
||||
return ({...booking, startTime:booking.startTime.toISOString(), endTime:booking.endTime.toISOString(),})
|
||||
});
|
||||
|
||||
return { props: { bookings } };
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
PlusIcon,
|
||||
UserIcon,
|
||||
} from "@heroicons/react/solid";
|
||||
import Loader from '@components/Loader';
|
||||
import Loader from "@components/Loader";
|
||||
|
||||
export default function Availability({ user, types }) {
|
||||
const [session, loading] = useSession();
|
||||
|
@ -42,7 +42,7 @@ export default function Availability({ user, types }) {
|
|||
|
||||
// TODO: Add validation
|
||||
|
||||
const response = await fetch("/api/availability/eventtype", {
|
||||
await fetch("/api/availability/eventtype", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
title: enteredTitle,
|
||||
|
@ -66,7 +66,7 @@ export default function Availability({ user, types }) {
|
|||
}
|
||||
|
||||
if (loading) {
|
||||
return <Loader/>;
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -79,12 +79,14 @@ export default function Availability({ user, types }) {
|
|||
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>
|
||||
types.length !== 0 && (
|
||||
<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">
|
||||
|
@ -261,9 +263,9 @@ export default function Availability({ user, types }) {
|
|||
</ul>
|
||||
</div>
|
||||
{types.length === 0 && (
|
||||
<div className="text-center max-w-lg mx-auto">
|
||||
<div className="md:py-20">
|
||||
<svg
|
||||
className="mx-auto mb-4 w-32 h-32"
|
||||
className="w-1/2 md:w-32 mx-auto block mb-4"
|
||||
viewBox="0 0 132 132"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
|
@ -500,12 +502,19 @@ export default function Availability({ user, types }) {
|
|||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<h3 className="mt-2 text-xl font-bold text-neutral-900">Create your first event type</h3>
|
||||
<p className="mt-1 text-md text-neutral-600">
|
||||
Event types enable you to share links that show available times on your calendar and allow
|
||||
people to make bookings with you.
|
||||
</p>
|
||||
<div className="text-center block md:max-w-screen-sm mx-auto">
|
||||
<h3 className="mt-2 text-xl font-bold text-neutral-900">Create your first event type</h3>
|
||||
<p className="mt-1 text-md text-neutral-600">
|
||||
Event types enable you to share links that show available times on your calendar and allow
|
||||
people to make bookings with you.
|
||||
</p>
|
||||
<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>
|
||||
)}
|
||||
{showAddModal && (
|
||||
|
|
|
@ -12,6 +12,11 @@ import TimezoneSelect from "react-timezone-select";
|
|||
import { UsernameInput } from "../../components/ui/UsernameInput";
|
||||
import ErrorAlert from "../../components/ui/alerts/Error";
|
||||
|
||||
const themeOptions = [
|
||||
{ value: "light", label: "Light" },
|
||||
{ value: "dark", label: "Dark" },
|
||||
];
|
||||
|
||||
export default function Settings(props) {
|
||||
const [successModalOpen, setSuccessModalOpen] = useState(false);
|
||||
const usernameRef = useRef<HTMLInputElement>();
|
||||
|
@ -19,18 +24,13 @@ export default function Settings(props) {
|
|||
const descriptionRef = useRef<HTMLTextAreaElement>();
|
||||
const avatarRef = useRef<HTMLInputElement>();
|
||||
const hideBrandingRef = useRef<HTMLInputElement>();
|
||||
const [selectedTheme, setSelectedTheme] = useState({ value: "" });
|
||||
const [selectedTheme, setSelectedTheme] = useState({ value: props.user.theme });
|
||||
const [selectedTimeZone, setSelectedTimeZone] = useState({ value: props.user.timeZone });
|
||||
const [selectedWeekStartDay, setSelectedWeekStartDay] = useState({ value: "" });
|
||||
const [selectedWeekStartDay, setSelectedWeekStartDay] = useState({ value: props.user.weekStart });
|
||||
|
||||
const [hasErrors, setHasErrors] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
|
||||
const themeOptions = [
|
||||
{ value: "light", label: "Light" },
|
||||
{ value: "dark", label: "Dark" },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedTheme(
|
||||
props.user.theme ? themeOptions.find((theme) => theme.value === props.user.theme) : null
|
||||
|
@ -179,6 +179,7 @@ export default function Settings(props) {
|
|||
id="theme"
|
||||
isDisabled={!selectedTheme}
|
||||
defaultValue={selectedTheme || themeOptions[0]}
|
||||
value={selectedTheme || themeOptions[0]}
|
||||
onChange={setSelectedTheme}
|
||||
className="shadow-sm focus:ring-neutral-500 focus:border-neutral-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-sm"
|
||||
options={themeOptions}
|
||||
|
|
87
yarn.lock
87
yarn.lock
|
@ -753,6 +753,93 @@
|
|||
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-2.26.0-23.9b816b3aa13cc270074f172f30d6eda8a8ce867d.tgz#cfdacfad3acc0f3bf1d7710aa8f3852fd85ac6d9"
|
||||
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":
|
||||
version "1.8.3"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d"
|
||||
|
|
Loading…
Reference in a new issue