new EmptyScreen component, using it in /bookings (#708)
* new EmptyScreen component, using it in /bookings * Linting fixes Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
This commit is contained in:
parent
96fd7ecf41
commit
3c089af58a
10 changed files with 303 additions and 261 deletions
26
components/EmptyScreen.tsx
Normal file
26
components/EmptyScreen.tsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import { SVGComponent } from "@lib/types/SVGComponent";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function EmptyScreen({
|
||||||
|
Icon,
|
||||||
|
headline,
|
||||||
|
description,
|
||||||
|
}: {
|
||||||
|
Icon: SVGComponent;
|
||||||
|
headline: string;
|
||||||
|
description: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="min-h-80 border border-dashed rounded-sm flex justify-center items-center flex-col">
|
||||||
|
<div className="bg-white w-[72px] h-[72px] flex justify-center items-center rounded-full">
|
||||||
|
<Icon className="inline-block w-10 h-10 bg-white" />
|
||||||
|
</div>
|
||||||
|
<div className="max-w-[500px] text-center">
|
||||||
|
<h2 className="font-cal text-xl leading-7 font-bold mt-6 mb-3">{headline}</h2>
|
||||||
|
<p className="leading-6">{description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,9 +1,8 @@
|
||||||
import classNames from "@lib/classNames";
|
import classNames from "@lib/classNames";
|
||||||
|
import { SVGComponent } from "@lib/types/SVGComponent";
|
||||||
import Link, { LinkProps } from "next/link";
|
import Link, { LinkProps } from "next/link";
|
||||||
import React, { forwardRef } from "react";
|
import React, { forwardRef } from "react";
|
||||||
|
|
||||||
type SVGComponent = React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
|
|
||||||
|
|
||||||
export type ButtonProps = {
|
export type ButtonProps = {
|
||||||
color?: "primary" | "secondary" | "minimal" | "warn";
|
color?: "primary" | "secondary" | "minimal" | "warn";
|
||||||
size?: "base" | "sm" | "lg" | "fab";
|
size?: "base" | "sm" | "lg" | "fab";
|
||||||
|
|
|
@ -59,7 +59,8 @@ export default class EventAttendeeMail extends EventMail {
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>When</td>
|
<td>When</td>
|
||||||
<td>${this.getInviteeStart().format("dddd, LL")}<br>${this.getInviteeStart().format("h:mma")} (${this.calEvent.attendees[0].timeZone
|
<td>${this.getInviteeStart().format("dddd, LL")}<br>${this.getInviteeStart().format("h:mma")} (${
|
||||||
|
this.calEvent.attendees[0].timeZone
|
||||||
})</td>
|
})</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -141,8 +142,9 @@ export default class EventAttendeeMail extends EventMail {
|
||||||
to: `${this.calEvent.attendees[0].name} <${this.calEvent.attendees[0].email}>`,
|
to: `${this.calEvent.attendees[0].name} <${this.calEvent.attendees[0].email}>`,
|
||||||
from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`,
|
from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`,
|
||||||
replyTo: this.calEvent.organizer.email,
|
replyTo: this.calEvent.organizer.email,
|
||||||
subject: `Confirmed: ${this.calEvent.type} with ${this.calEvent.team?.name || this.calEvent.organizer.name
|
subject: `Confirmed: ${this.calEvent.type} with ${
|
||||||
} on ${this.getInviteeStart().format("LT dddd, LL")}`,
|
this.calEvent.team?.name || this.calEvent.organizer.name
|
||||||
|
} on ${this.getInviteeStart().format("LT dddd, LL")}`,
|
||||||
html: this.getHtmlRepresentation(),
|
html: this.getHtmlRepresentation(),
|
||||||
text: this.getPlainTextRepresentation(),
|
text: this.getPlainTextRepresentation(),
|
||||||
};
|
};
|
||||||
|
|
|
@ -51,8 +51,9 @@ export default class EventOrganizerMail extends EventMail {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getAdditionalFooter(): string {
|
protected getAdditionalFooter(): string {
|
||||||
return `<p style="color: #4b5563; margin-top: 20px;">Need to make a change? <a href=${process.env.BASE_URL + "/bookings"
|
return `<p style="color: #4b5563; margin-top: 20px;">Need to make a change? <a href=${
|
||||||
} style="color: #161e2e;">Manage my bookings</a></p>`;
|
process.env.BASE_URL + "/bookings"
|
||||||
|
} style="color: #161e2e;">Manage my bookings</a></p>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getImage(): string {
|
protected getImage(): string {
|
||||||
|
@ -107,12 +108,14 @@ export default class EventOrganizerMail extends EventMail {
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>When</td>
|
<td>When</td>
|
||||||
<td>${this.getOrganizerStart().format("dddd, LL")}<br>${this.getOrganizerStart().format("h:mma")} (${this.calEvent.organizer.timeZone
|
<td>${this.getOrganizerStart().format("dddd, LL")}<br>${this.getOrganizerStart().format("h:mma")} (${
|
||||||
|
this.calEvent.organizer.timeZone
|
||||||
})</td>
|
})</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Who</td>
|
<td>Who</td>
|
||||||
<td>${this.calEvent.attendees[0].name}<br /><small><a href="mailto:${this.calEvent.attendees[0].email
|
<td>${this.calEvent.attendees[0].name}<br /><small><a href="mailto:${
|
||||||
|
this.calEvent.attendees[0].email
|
||||||
}">${this.calEvent.attendees[0].email}</a></small></td>
|
}">${this.calEvent.attendees[0].email}</a></small></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
|
@ -68,8 +68,9 @@ export default class EventRejectionMail extends EventMail {
|
||||||
to: `${this.calEvent.attendees[0].name} <${this.calEvent.attendees[0].email}>`,
|
to: `${this.calEvent.attendees[0].name} <${this.calEvent.attendees[0].email}>`,
|
||||||
from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`,
|
from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`,
|
||||||
replyTo: this.calEvent.organizer.email,
|
replyTo: this.calEvent.organizer.email,
|
||||||
subject: `Rejected: ${this.calEvent.type} with ${this.calEvent.organizer.name
|
subject: `Rejected: ${this.calEvent.type} with ${
|
||||||
} on ${this.getInviteeStart().format("dddd, LL")}`,
|
this.calEvent.organizer.name
|
||||||
|
} on ${this.getInviteeStart().format("dddd, LL")}`,
|
||||||
html: this.getHtmlRepresentation(),
|
html: this.getHtmlRepresentation(),
|
||||||
text: this.getPlainTextRepresentation(),
|
text: this.getPlainTextRepresentation(),
|
||||||
};
|
};
|
||||||
|
|
1
lib/types/SVGComponent.ts
Normal file
1
lib/types/SVGComponent.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export type SVGComponent = React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
|
|
@ -115,9 +115,7 @@ export default function Signup(props) {
|
||||||
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"
|
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
|
<a
|
||||||
onClick={() =>
|
onClick={() => signIn("Cal.com", { callbackUrl: (router.query.callbackUrl || "") as string })}
|
||||||
signIn("Cal.com", { 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">
|
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
|
Login instead
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -8,16 +8,19 @@ import { Fragment } from "react";
|
||||||
import { Menu, Transition } from "@headlessui/react";
|
import { Menu, Transition } from "@headlessui/react";
|
||||||
import { DotsHorizontalIcon } from "@heroicons/react/solid";
|
import { DotsHorizontalIcon } from "@heroicons/react/solid";
|
||||||
import classNames from "@lib/classNames";
|
import classNames from "@lib/classNames";
|
||||||
import { ClockIcon, XIcon, CheckIcon, BanIcon } from "@heroicons/react/outline";
|
import { ClockIcon, CalendarIcon, XIcon, CheckIcon, BanIcon } from "@heroicons/react/outline";
|
||||||
import Loader from "@components/Loader";
|
import Loader from "@components/Loader";
|
||||||
import { Button } from "@components/ui/Button";
|
import { Button } from "@components/ui/Button";
|
||||||
import { getSession } from "@lib/auth";
|
import { getSession } from "@lib/auth";
|
||||||
import { BookingStatus, User } from "@prisma/client";
|
import { BookingStatus, User } from "@prisma/client";
|
||||||
|
import EmptyScreen from "@components/EmptyScreen";
|
||||||
|
|
||||||
export default function Bookings({ bookings }) {
|
export default function Bookings({ bookings }) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const [session, loading] = useSession();
|
const [session, loading] = useSession();
|
||||||
|
|
||||||
|
const isEmpty = Object.keys(bookings).length === 0;
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
|
@ -43,227 +46,235 @@ export default function Bookings({ bookings }) {
|
||||||
<div className="-mx-4 sm:mx-auto flex flex-col">
|
<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="-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="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
|
||||||
<div className="border border-gray-200 overflow-hidden border-b rounded-sm">
|
{isEmpty ? (
|
||||||
<table className="min-w-full divide-y divide-gray-200">
|
<EmptyScreen
|
||||||
<tbody className="bg-white divide-y divide-gray-200" data-testid="bookings">
|
Icon={CalendarIcon}
|
||||||
{bookings
|
headline="No upcoming bookings, yet"
|
||||||
.filter((booking) => booking.status !== BookingStatus.CANCELLED)
|
description="You have no upcoming bookings. As soon as someone books a time with you it will show up here."
|
||||||
.map((booking) => (
|
/>
|
||||||
<tr key={booking.id}>
|
) : (
|
||||||
<td className={"px-6 py-4" + (booking.rejected ? " line-through" : "")}>
|
<div className="border border-gray-200 overflow-hidden border-b rounded-sm">
|
||||||
{!booking.confirmed && !booking.rejected && (
|
<table className="min-w-full divide-y divide-gray-200">
|
||||||
<span className="mb-2 inline-flex items-center px-1.5 py-0.5 rounded-sm text-xs font-medium bg-yellow-100 text-yellow-800">
|
<tbody className="bg-white divide-y divide-gray-200" data-testid="bookings">
|
||||||
Unconfirmed
|
{bookings
|
||||||
</span>
|
.filter((booking) => booking.status !== BookingStatus.CANCELLED)
|
||||||
)}
|
.map((booking) => (
|
||||||
<div className="text-sm text-neutral-900 font-medium truncate max-w-60 md:max-w-96">
|
<tr key={booking.id}>
|
||||||
{booking.eventType?.team && <strong>{booking.eventType.team.name}: </strong>}
|
<td className={"px-6 py-4" + (booking.rejected ? " line-through" : "")}>
|
||||||
{booking.title}
|
{!booking.confirmed && !booking.rejected && (
|
||||||
</div>
|
<span className="mb-2 inline-flex items-center px-1.5 py-0.5 rounded-sm text-xs font-medium bg-yellow-100 text-yellow-800">
|
||||||
<div className="sm:hidden">
|
Unconfirmed
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<div className="text-sm text-neutral-900 font-medium truncate max-w-60 md:max-w-96">
|
||||||
|
{booking.eventType?.team && <strong>{booking.eventType.team.name}: </strong>}
|
||||||
|
{booking.title}
|
||||||
|
</div>
|
||||||
|
<div className="sm:hidden">
|
||||||
|
<div className="text-sm text-gray-900">
|
||||||
|
{dayjs(booking.startTime).format("D MMMM YYYY")}:{" "}
|
||||||
|
<small className="text-sm text-gray-500">
|
||||||
|
{dayjs(booking.startTime).format("HH:mm")} -{" "}
|
||||||
|
{dayjs(booking.endTime).format("HH:mm")}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{booking.attendees.length !== 0 && (
|
||||||
|
<div className="text-sm text-blue-500">
|
||||||
|
<a href={"mailto:" + booking.attendees[0].email}>
|
||||||
|
{booking.attendees[0].email}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td className="hidden sm:table-cell px-6 py-4 whitespace-nowrap">
|
||||||
<div className="text-sm text-gray-900">
|
<div className="text-sm text-gray-900">
|
||||||
{dayjs(booking.startTime).format("D MMMM YYYY")}:{" "}
|
{dayjs(booking.startTime).format("D MMMM YYYY")}
|
||||||
<small className="text-sm text-gray-500">
|
|
||||||
{dayjs(booking.startTime).format("HH:mm")} -{" "}
|
|
||||||
{dayjs(booking.endTime).format("HH:mm")}
|
|
||||||
</small>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="text-sm text-gray-500">
|
||||||
{booking.attendees.length !== 0 && (
|
{dayjs(booking.startTime).format("HH:mm")} -{" "}
|
||||||
<div className="text-sm text-blue-500">
|
{dayjs(booking.endTime).format("HH:mm")}
|
||||||
<a href={"mailto:" + booking.attendees[0].email}>
|
|
||||||
{booking.attendees[0].email}
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</td>
|
||||||
</td>
|
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||||
<td className="hidden sm:table-cell px-6 py-4 whitespace-nowrap">
|
{!booking.confirmed && !booking.rejected && (
|
||||||
<div className="text-sm text-gray-900">
|
<>
|
||||||
{dayjs(booking.startTime).format("D MMMM YYYY")}
|
<div className="space-x-2 hidden lg:block">
|
||||||
</div>
|
<Button
|
||||||
<div className="text-sm text-gray-500">
|
onClick={() => confirmBookingHandler(booking, true)}
|
||||||
{dayjs(booking.startTime).format("HH:mm")} -{" "}
|
StartIcon={CheckIcon}
|
||||||
{dayjs(booking.endTime).format("HH:mm")}
|
color="secondary">
|
||||||
</div>
|
Confirm
|
||||||
</td>
|
</Button>
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
<Button
|
||||||
{!booking.confirmed && !booking.rejected && (
|
onClick={() => confirmBookingHandler(booking, false)}
|
||||||
<>
|
StartIcon={BanIcon}
|
||||||
<div className="space-x-2 hidden lg:block">
|
color="secondary">
|
||||||
<Button
|
Reject
|
||||||
onClick={() => confirmBookingHandler(booking, true)}
|
</Button>
|
||||||
StartIcon={CheckIcon}
|
</div>
|
||||||
color="secondary">
|
<Menu as="div" className="inline-block lg:hidden text-left ">
|
||||||
Confirm
|
{({ open }) => (
|
||||||
</Button>
|
<>
|
||||||
<Button
|
<div>
|
||||||
onClick={() => confirmBookingHandler(booking, false)}
|
<Menu.Button className="text-neutral-400 mt-1 p-2 border border-transparent hover:border-gray-200">
|
||||||
StartIcon={BanIcon}
|
<span className="sr-only">Open options</span>
|
||||||
color="secondary">
|
<DotsHorizontalIcon className="h-5 w-5" aria-hidden="true" />
|
||||||
Reject
|
</Menu.Button>
|
||||||
</Button>
|
</div>
|
||||||
</div>
|
<Transition
|
||||||
<Menu as="div" className="inline-block lg:hidden text-left ">
|
show={open}
|
||||||
{({ open }) => (
|
as={Fragment}
|
||||||
<>
|
enter="transition ease-out duration-100"
|
||||||
<div>
|
enterFrom="transform opacity-0 scale-95"
|
||||||
<Menu.Button className="text-neutral-400 mt-1 p-2 border border-transparent hover:border-gray-200">
|
enterTo="transform opacity-100 scale-100"
|
||||||
<span className="sr-only">Open options</span>
|
leave="transition ease-in duration-75"
|
||||||
<DotsHorizontalIcon className="h-5 w-5" aria-hidden="true" />
|
leaveFrom="transform opacity-100 scale-100"
|
||||||
</Menu.Button>
|
leaveTo="transform opacity-0 scale-95">
|
||||||
</div>
|
<Menu.Items
|
||||||
<Transition
|
static
|
||||||
show={open}
|
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">
|
||||||
as={Fragment}
|
<div className="py-1">
|
||||||
enter="transition ease-out duration-100"
|
<Menu.Item>
|
||||||
enterFrom="transform opacity-0 scale-95"
|
{({ active }) => (
|
||||||
enterTo="transform opacity-100 scale-100"
|
<span
|
||||||
leave="transition ease-in duration-75"
|
onClick={() => confirmBookingHandler(booking, true)}
|
||||||
leaveFrom="transform opacity-100 scale-100"
|
className={classNames(
|
||||||
leaveTo="transform opacity-0 scale-95">
|
active
|
||||||
<Menu.Items
|
? "bg-neutral-100 text-neutral-900"
|
||||||
static
|
: "text-neutral-700",
|
||||||
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">
|
"group flex items-center px-4 py-2 text-sm font-medium"
|
||||||
<div className="py-1">
|
)}>
|
||||||
<Menu.Item>
|
<CheckIcon
|
||||||
{({ active }) => (
|
className="mr-3 h-5 w-5 text-neutral-400 group-hover:text-neutral-500"
|
||||||
<span
|
aria-hidden="true"
|
||||||
onClick={() => confirmBookingHandler(booking, true)}
|
/>
|
||||||
className={classNames(
|
Confirm
|
||||||
active
|
</span>
|
||||||
? "bg-neutral-100 text-neutral-900"
|
)}
|
||||||
: "text-neutral-700",
|
</Menu.Item>
|
||||||
"group flex items-center px-4 py-2 text-sm font-medium"
|
<Menu.Item>
|
||||||
)}>
|
{({ active }) => (
|
||||||
<CheckIcon
|
<span
|
||||||
className="mr-3 h-5 w-5 text-neutral-400 group-hover:text-neutral-500"
|
onClick={() => confirmBookingHandler(booking, false)}
|
||||||
aria-hidden="true"
|
className={classNames(
|
||||||
/>
|
active
|
||||||
Confirm
|
? "bg-neutral-100 text-neutral-900"
|
||||||
</span>
|
: "text-neutral-700",
|
||||||
)}
|
"group flex items-center px-4 py-2 text-sm w-full font-medium"
|
||||||
</Menu.Item>
|
)}>
|
||||||
<Menu.Item>
|
<BanIcon
|
||||||
{({ active }) => (
|
className="mr-3 h-5 w-5 text-neutral-400 group-hover:text-neutral-500"
|
||||||
<span
|
aria-hidden="true"
|
||||||
onClick={() => confirmBookingHandler(booking, false)}
|
/>
|
||||||
className={classNames(
|
Reject
|
||||||
active
|
</span>
|
||||||
? "bg-neutral-100 text-neutral-900"
|
)}
|
||||||
: "text-neutral-700",
|
</Menu.Item>
|
||||||
"group flex items-center px-4 py-2 text-sm w-full font-medium"
|
</div>
|
||||||
)}>
|
</Menu.Items>
|
||||||
<BanIcon
|
</Transition>
|
||||||
className="mr-3 h-5 w-5 text-neutral-400 group-hover:text-neutral-500"
|
</>
|
||||||
aria-hidden="true"
|
)}
|
||||||
/>
|
</Menu>
|
||||||
Reject
|
</>
|
||||||
</span>
|
)}
|
||||||
)}
|
{booking.confirmed && !booking.rejected && (
|
||||||
</Menu.Item>
|
<>
|
||||||
</div>
|
<div className="space-x-2 hidden lg:block">
|
||||||
</Menu.Items>
|
<Button
|
||||||
</Transition>
|
data-testid="cancel"
|
||||||
</>
|
href={"/cancel/" + booking.uid}
|
||||||
)}
|
StartIcon={XIcon}
|
||||||
</Menu>
|
color="secondary">
|
||||||
</>
|
Cancel
|
||||||
)}
|
</Button>
|
||||||
{booking.confirmed && !booking.rejected && (
|
<Button
|
||||||
<>
|
href={"reschedule/" + booking.uid}
|
||||||
<div className="space-x-2 hidden lg:block">
|
StartIcon={ClockIcon}
|
||||||
<Button
|
color="secondary">
|
||||||
data-testid="cancel"
|
Reschedule
|
||||||
href={"/cancel/" + booking.uid}
|
</Button>
|
||||||
StartIcon={XIcon}
|
</div>
|
||||||
color="secondary">
|
<Menu as="div" className="inline-block lg:hidden text-left ">
|
||||||
Cancel
|
{({ open }) => (
|
||||||
</Button>
|
<>
|
||||||
<Button
|
<div>
|
||||||
href={"reschedule/" + booking.uid}
|
<Menu.Button className="text-neutral-400 mt-1 p-2 border border-transparent hover:border-gray-200">
|
||||||
StartIcon={ClockIcon}
|
<span className="sr-only">Open options</span>
|
||||||
color="secondary">
|
<DotsHorizontalIcon className="h-5 w-5" aria-hidden="true" />
|
||||||
Reschedule
|
</Menu.Button>
|
||||||
</Button>
|
</div>
|
||||||
</div>
|
|
||||||
<Menu as="div" className="inline-block lg:hidden text-left ">
|
|
||||||
{({ open }) => (
|
|
||||||
<>
|
|
||||||
<div>
|
|
||||||
<Menu.Button className="text-neutral-400 mt-1 p-2 border border-transparent hover:border-gray-200">
|
|
||||||
<span className="sr-only">Open options</span>
|
|
||||||
<DotsHorizontalIcon className="h-5 w-5" aria-hidden="true" />
|
|
||||||
</Menu.Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Transition
|
<Transition
|
||||||
show={open}
|
show={open}
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="transition ease-out duration-100"
|
enter="transition ease-out duration-100"
|
||||||
enterFrom="transform opacity-0 scale-95"
|
enterFrom="transform opacity-0 scale-95"
|
||||||
enterTo="transform opacity-100 scale-100"
|
enterTo="transform opacity-100 scale-100"
|
||||||
leave="transition ease-in duration-75"
|
leave="transition ease-in duration-75"
|
||||||
leaveFrom="transform opacity-100 scale-100"
|
leaveFrom="transform opacity-100 scale-100"
|
||||||
leaveTo="transform opacity-0 scale-95">
|
leaveTo="transform opacity-0 scale-95">
|
||||||
<Menu.Items
|
<Menu.Items
|
||||||
static
|
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">
|
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">
|
<div className="py-1">
|
||||||
<Menu.Item>
|
<Menu.Item>
|
||||||
{({ active }) => (
|
{({ active }) => (
|
||||||
<a
|
<a
|
||||||
href={window.location.href + "/../cancel/" + booking.uid}
|
href={window.location.href + "/../cancel/" + booking.uid}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
active
|
active
|
||||||
? "bg-neutral-100 text-neutral-900"
|
? "bg-neutral-100 text-neutral-900"
|
||||||
: "text-neutral-700",
|
: "text-neutral-700",
|
||||||
"group flex items-center px-4 py-2 text-sm font-medium"
|
"group flex items-center px-4 py-2 text-sm font-medium"
|
||||||
)}>
|
)}>
|
||||||
<XIcon
|
<XIcon
|
||||||
className="mr-3 h-5 w-5 text-neutral-400 group-hover:text-neutral-500"
|
className="mr-3 h-5 w-5 text-neutral-400 group-hover:text-neutral-500"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
Cancel
|
Cancel
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item>
|
<Menu.Item>
|
||||||
{({ active }) => (
|
{({ active }) => (
|
||||||
<a
|
<a
|
||||||
href={
|
href={
|
||||||
window.location.href + "/../reschedule/" + booking.uid
|
window.location.href + "/../reschedule/" + booking.uid
|
||||||
}
|
}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
active
|
active
|
||||||
? "bg-neutral-100 text-neutral-900"
|
? "bg-neutral-100 text-neutral-900"
|
||||||
: "text-neutral-700",
|
: "text-neutral-700",
|
||||||
"group flex items-center px-4 py-2 text-sm w-full font-medium"
|
"group flex items-center px-4 py-2 text-sm w-full font-medium"
|
||||||
)}>
|
)}>
|
||||||
<ClockIcon
|
<ClockIcon
|
||||||
className="mr-3 h-5 w-5 text-neutral-400 group-hover:text-neutral-500"
|
className="mr-3 h-5 w-5 text-neutral-400 group-hover:text-neutral-500"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
Reschedule
|
Reschedule
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</div>
|
</div>
|
||||||
</Menu.Items>
|
</Menu.Items>
|
||||||
</Transition>
|
</Transition>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Menu>
|
</Menu>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{!booking.confirmed && booking.rejected && (
|
{!booking.confirmed && booking.rejected && (
|
||||||
<div className="text-sm text-gray-500">Rejected</div>
|
<div className="text-sm text-gray-500">Rejected</div>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -342,11 +342,11 @@ const CreateNewEventDialog = ({ profiles, canAddEvents }: { profiles: Profile[];
|
||||||
data-testid="new-event-type"
|
data-testid="new-event-type"
|
||||||
{...(canAddEvents
|
{...(canAddEvents
|
||||||
? {
|
? {
|
||||||
href: modalOpen.hrefOn,
|
href: modalOpen.hrefOn,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
disabled: true,
|
disabled: true,
|
||||||
})}
|
})}
|
||||||
StartIcon={PlusIcon}>
|
StartIcon={PlusIcon}>
|
||||||
New event type
|
New event type
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -372,8 +372,8 @@ const CreateNewEventDialog = ({ profiles, canAddEvents }: { profiles: Profile[];
|
||||||
eventPage: profile.slug,
|
eventPage: profile.slug,
|
||||||
...(profile.teamId
|
...(profile.teamId
|
||||||
? {
|
? {
|
||||||
teamId: profile.teamId,
|
teamId: profile.teamId,
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -681,13 +681,13 @@ export async function getServerSideProps(context) {
|
||||||
eventTypes: user.eventTypes.concat(typesRaw).map((type, index) =>
|
eventTypes: user.eventTypes.concat(typesRaw).map((type, index) =>
|
||||||
user.plan === "FREE" && index > 0
|
user.plan === "FREE" && index > 0
|
||||||
? {
|
? {
|
||||||
...type,
|
...type,
|
||||||
$disabled: true,
|
$disabled: true,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
...type,
|
...type,
|
||||||
$disabled: false,
|
$disabled: false,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
metadata: {
|
metadata: {
|
||||||
membershipCount: 1,
|
membershipCount: 1,
|
||||||
|
|
|
@ -135,9 +135,10 @@ export default function Success(props: inferSSRProps<typeof getServerSideProps>)
|
||||||
`https://calendar.google.com/calendar/r/eventedit?dates=${date
|
`https://calendar.google.com/calendar/r/eventedit?dates=${date
|
||||||
.utc()
|
.utc()
|
||||||
.format("YYYYMMDDTHHmmss[Z]")}/${date
|
.format("YYYYMMDDTHHmmss[Z]")}/${date
|
||||||
.add(props.eventType.length, "minute")
|
.add(props.eventType.length, "minute")
|
||||||
.utc()
|
.utc()
|
||||||
.format("YYYYMMDDTHHmmss[Z]")}&text=${eventName}&details=${props.eventType.description
|
.format("YYYYMMDDTHHmmss[Z]")}&text=${eventName}&details=${
|
||||||
|
props.eventType.description
|
||||||
}` + (location ? "&location=" + encodeURIComponent(location) : "")
|
}` + (location ? "&location=" + encodeURIComponent(location) : "")
|
||||||
}>
|
}>
|
||||||
<a className="mx-2 rounded-sm border border-neutral-200 dark:border-neutral-700 dark:text-white py-2 px-3">
|
<a className="mx-2 rounded-sm border border-neutral-200 dark:border-neutral-700 dark:text-white py-2 px-3">
|
||||||
|
@ -155,13 +156,13 @@ export default function Success(props: inferSSRProps<typeof getServerSideProps>)
|
||||||
href={
|
href={
|
||||||
encodeURI(
|
encodeURI(
|
||||||
"https://outlook.live.com/calendar/0/deeplink/compose?body=" +
|
"https://outlook.live.com/calendar/0/deeplink/compose?body=" +
|
||||||
props.eventType.description +
|
props.eventType.description +
|
||||||
"&enddt=" +
|
"&enddt=" +
|
||||||
date.add(props.eventType.length, "minute").utc().format() +
|
date.add(props.eventType.length, "minute").utc().format() +
|
||||||
"&path=%2Fcalendar%2Faction%2Fcompose&rru=addevent&startdt=" +
|
"&path=%2Fcalendar%2Faction%2Fcompose&rru=addevent&startdt=" +
|
||||||
date.utc().format() +
|
date.utc().format() +
|
||||||
"&subject=" +
|
"&subject=" +
|
||||||
eventName
|
eventName
|
||||||
) + (location ? "&location=" + location : "")
|
) + (location ? "&location=" + location : "")
|
||||||
}>
|
}>
|
||||||
<a
|
<a
|
||||||
|
@ -181,13 +182,13 @@ export default function Success(props: inferSSRProps<typeof getServerSideProps>)
|
||||||
href={
|
href={
|
||||||
encodeURI(
|
encodeURI(
|
||||||
"https://outlook.office.com/calendar/0/deeplink/compose?body=" +
|
"https://outlook.office.com/calendar/0/deeplink/compose?body=" +
|
||||||
props.eventType.description +
|
props.eventType.description +
|
||||||
"&enddt=" +
|
"&enddt=" +
|
||||||
date.add(props.eventType.length, "minute").utc().format() +
|
date.add(props.eventType.length, "minute").utc().format() +
|
||||||
"&path=%2Fcalendar%2Faction%2Fcompose&rru=addevent&startdt=" +
|
"&path=%2Fcalendar%2Faction%2Fcompose&rru=addevent&startdt=" +
|
||||||
date.utc().format() +
|
date.utc().format() +
|
||||||
"&subject=" +
|
"&subject=" +
|
||||||
eventName
|
eventName
|
||||||
) + (location ? "&location=" + location : "")
|
) + (location ? "&location=" + location : "")
|
||||||
}>
|
}>
|
||||||
<a
|
<a
|
||||||
|
|
Loading…
Reference in a new issue