2022-04-14 21:25:24 +00:00
|
|
|
|
import {
|
|
|
|
|
CalendarIcon,
|
|
|
|
|
ClockIcon,
|
|
|
|
|
CreditCardIcon,
|
|
|
|
|
ExclamationIcon,
|
|
|
|
|
InformationCircleIcon,
|
|
|
|
|
} from "@heroicons/react/solid";
|
2022-04-27 15:19:04 +00:00
|
|
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
2021-09-14 08:45:28 +00:00
|
|
|
|
import { EventTypeCustomInputType } from "@prisma/client";
|
2022-02-01 21:48:40 +00:00
|
|
|
|
import { useContracts } from "contexts/contractsContext";
|
2021-09-14 08:45:28 +00:00
|
|
|
|
import dayjs from "dayjs";
|
2022-03-15 14:39:20 +00:00
|
|
|
|
import { useSession } from "next-auth/react";
|
2022-01-19 16:24:01 +00:00
|
|
|
|
import dynamic from "next/dynamic";
|
2021-09-22 19:52:38 +00:00
|
|
|
|
import Head from "next/head";
|
|
|
|
|
import { useRouter } from "next/router";
|
2022-03-24 02:27:35 +00:00
|
|
|
|
import { useEffect, useMemo, useState } from "react";
|
2021-12-03 10:15:20 +00:00
|
|
|
|
import { Controller, useForm, useWatch } from "react-hook-form";
|
2021-09-22 19:52:38 +00:00
|
|
|
|
import { FormattedNumber, IntlProvider } from "react-intl";
|
2021-09-14 08:45:28 +00:00
|
|
|
|
import { ReactMultiEmail } from "react-multi-email";
|
2021-12-03 10:15:20 +00:00
|
|
|
|
import { useMutation } from "react-query";
|
2022-04-27 15:19:04 +00:00
|
|
|
|
import { z } from "zod";
|
2021-09-22 19:52:38 +00:00
|
|
|
|
|
2022-04-25 04:33:00 +00:00
|
|
|
|
import {
|
|
|
|
|
useIsEmbed,
|
|
|
|
|
useEmbedStyles,
|
|
|
|
|
useIsBackgroundTransparent,
|
|
|
|
|
useEmbedType,
|
|
|
|
|
useEmbedNonStylesConfig,
|
|
|
|
|
} from "@calcom/embed-core";
|
2022-04-08 05:33:24 +00:00
|
|
|
|
import classNames from "@calcom/lib/classNames";
|
2022-04-06 17:20:30 +00:00
|
|
|
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
2022-03-24 02:27:35 +00:00
|
|
|
|
import { HttpError } from "@calcom/lib/http-error";
|
2022-03-09 22:56:05 +00:00
|
|
|
|
import { createPaymentLink } from "@calcom/stripe/client";
|
2022-03-16 23:36:43 +00:00
|
|
|
|
import { Button } from "@calcom/ui/Button";
|
|
|
|
|
import { EmailInput, Form } from "@calcom/ui/form/fields";
|
2021-09-22 19:52:38 +00:00
|
|
|
|
|
2021-09-14 08:45:28 +00:00
|
|
|
|
import { asStringOrNull } from "@lib/asStringOrNull";
|
|
|
|
|
import { timeZone } from "@lib/clock";
|
2021-12-03 16:18:31 +00:00
|
|
|
|
import { ensureArray } from "@lib/ensureArray";
|
2021-09-14 08:45:28 +00:00
|
|
|
|
import useTheme from "@lib/hooks/useTheme";
|
2021-09-22 19:52:38 +00:00
|
|
|
|
import { LocationType } from "@lib/location";
|
|
|
|
|
import createBooking from "@lib/mutations/bookings/create-booking";
|
2022-04-14 21:25:24 +00:00
|
|
|
|
import { parseDate } from "@lib/parseDate";
|
2021-12-03 10:15:20 +00:00
|
|
|
|
import slugify from "@lib/slugify";
|
2021-09-22 19:52:38 +00:00
|
|
|
|
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
|
|
|
|
|
|
2021-11-16 08:51:46 +00:00
|
|
|
|
import CustomBranding from "@components/CustomBranding";
|
2021-09-22 19:52:38 +00:00
|
|
|
|
import AvatarGroup from "@components/ui/AvatarGroup";
|
2022-04-06 12:37:06 +00:00
|
|
|
|
import type PhoneInputType from "@components/ui/form/PhoneInput";
|
2021-09-22 19:52:38 +00:00
|
|
|
|
|
2021-09-22 18:36:13 +00:00
|
|
|
|
import { BookPageProps } from "../../../pages/[user]/book";
|
|
|
|
|
import { TeamBookingPageProps } from "../../../pages/team/[slug]/book";
|
2021-09-14 08:45:28 +00:00
|
|
|
|
|
2022-01-19 16:24:01 +00:00
|
|
|
|
/** These are like 40kb that not every user needs */
|
2022-04-06 12:37:06 +00:00
|
|
|
|
const PhoneInput = dynamic(
|
|
|
|
|
() => import("@components/ui/form/PhoneInput")
|
|
|
|
|
) as unknown as typeof PhoneInputType;
|
2022-01-19 16:24:01 +00:00
|
|
|
|
|
2021-09-22 18:36:13 +00:00
|
|
|
|
type BookingPageProps = BookPageProps | TeamBookingPageProps;
|
|
|
|
|
|
2022-02-01 21:48:40 +00:00
|
|
|
|
type BookingFormValues = {
|
|
|
|
|
name: string;
|
|
|
|
|
email: string;
|
|
|
|
|
notes?: string;
|
|
|
|
|
locationType?: LocationType;
|
|
|
|
|
guests?: string[];
|
|
|
|
|
phone?: string;
|
|
|
|
|
customInputs?: {
|
|
|
|
|
[key: string]: string;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2022-04-06 17:20:30 +00:00
|
|
|
|
const BookingPage = ({
|
|
|
|
|
eventType,
|
|
|
|
|
booking,
|
|
|
|
|
profile,
|
|
|
|
|
isDynamicGroupBooking,
|
|
|
|
|
locationLabels,
|
|
|
|
|
}: BookingPageProps) => {
|
2021-10-25 13:05:21 +00:00
|
|
|
|
const { t, i18n } = useLocale();
|
2022-04-08 05:33:24 +00:00
|
|
|
|
const isEmbed = useIsEmbed();
|
2022-04-25 04:33:00 +00:00
|
|
|
|
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
|
|
|
|
|
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
|
2021-09-14 08:45:28 +00:00
|
|
|
|
const router = useRouter();
|
2022-02-01 21:48:40 +00:00
|
|
|
|
const { contracts } = useContracts();
|
2022-03-15 14:39:20 +00:00
|
|
|
|
const { data: session } = useSession();
|
2022-04-08 05:33:24 +00:00
|
|
|
|
const isBackgroundTransparent = useIsBackgroundTransparent();
|
|
|
|
|
|
2022-02-03 23:23:20 +00:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (eventType.metadata.smartContractAddress) {
|
|
|
|
|
const eventOwner = eventType.users[0];
|
|
|
|
|
|
|
|
|
|
if (!contracts[(eventType.metadata.smartContractAddress || null) as number])
|
|
|
|
|
/* @ts-ignore */
|
|
|
|
|
router.replace(`/${eventOwner.username}`);
|
|
|
|
|
}
|
|
|
|
|
}, [contracts, eventType.metadata.smartContractAddress, router]);
|
|
|
|
|
|
2021-12-03 10:15:20 +00:00
|
|
|
|
const mutation = useMutation(createBooking, {
|
2022-02-18 16:53:45 +00:00
|
|
|
|
onSuccess: async (responseData) => {
|
|
|
|
|
const { attendees, paymentUid } = responseData;
|
2021-12-03 10:15:20 +00:00
|
|
|
|
if (paymentUid) {
|
|
|
|
|
return await router.push(
|
|
|
|
|
createPaymentLink({
|
|
|
|
|
paymentUid,
|
|
|
|
|
date,
|
|
|
|
|
name: attendees[0].name,
|
|
|
|
|
absolute: false,
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const location = (function humanReadableLocation(location) {
|
|
|
|
|
if (!location) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (location.includes("integration")) {
|
|
|
|
|
return t("web_conferencing_details_to_follow");
|
|
|
|
|
}
|
|
|
|
|
return location;
|
|
|
|
|
})(responseData.location);
|
|
|
|
|
|
|
|
|
|
return router.push({
|
|
|
|
|
pathname: "/success",
|
|
|
|
|
query: {
|
|
|
|
|
date,
|
2022-03-15 14:39:20 +00:00
|
|
|
|
type: eventType.id,
|
2022-04-06 17:20:30 +00:00
|
|
|
|
eventSlug: eventType.slug,
|
2022-03-15 14:39:20 +00:00
|
|
|
|
user: profile.slug,
|
2021-12-03 10:15:20 +00:00
|
|
|
|
reschedule: !!rescheduleUid,
|
|
|
|
|
name: attendees[0].name,
|
|
|
|
|
email: attendees[0].email,
|
|
|
|
|
location,
|
2022-04-08 16:50:10 +00:00
|
|
|
|
eventName: profile.eventName || "",
|
2021-12-03 10:15:20 +00:00
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const rescheduleUid = router.query.rescheduleUid as string;
|
2022-03-15 14:39:20 +00:00
|
|
|
|
const { isReady, Theme } = useTheme(profile.theme);
|
2021-09-14 08:45:28 +00:00
|
|
|
|
const date = asStringOrNull(router.query.date);
|
|
|
|
|
|
2022-03-15 14:39:20 +00:00
|
|
|
|
const [guestToggle, setGuestToggle] = useState(booking && booking.attendees.length > 1);
|
2021-09-14 08:45:28 +00:00
|
|
|
|
|
2022-03-15 14:39:20 +00:00
|
|
|
|
const eventTypeDetail = { isWeb3Active: false, ...eventType };
|
2022-02-01 21:48:40 +00:00
|
|
|
|
|
2022-03-31 19:57:12 +00:00
|
|
|
|
type Location = { type: LocationType; address?: string; link?: string };
|
2021-12-03 10:15:20 +00:00
|
|
|
|
// it would be nice if Prisma at some point in the future allowed for Json<Location>; as of now this is not the case.
|
2021-12-21 00:59:06 +00:00
|
|
|
|
const locations: Location[] = useMemo(
|
2022-03-15 14:39:20 +00:00
|
|
|
|
() => (eventType.locations as Location[]) || [],
|
|
|
|
|
[eventType.locations]
|
2021-09-14 08:45:28 +00:00
|
|
|
|
);
|
|
|
|
|
|
2021-12-03 10:15:20 +00:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (router.query.guest) {
|
|
|
|
|
setGuestToggle(true);
|
|
|
|
|
}
|
|
|
|
|
}, [router.query.guest]);
|
2021-09-14 08:45:28 +00:00
|
|
|
|
|
2021-12-03 10:15:20 +00:00
|
|
|
|
const telemetry = useTelemetry();
|
2021-09-14 08:45:28 +00:00
|
|
|
|
|
|
|
|
|
const locationInfo = (type: LocationType) => locations.find((location) => location.type === type);
|
2022-03-16 13:18:11 +00:00
|
|
|
|
const loggedInIsOwner = eventType?.users[0]?.name === session?.user?.name;
|
2022-04-14 21:25:24 +00:00
|
|
|
|
const guestListEmails = !isDynamicGroupBooking
|
|
|
|
|
? booking?.attendees.slice(1).map((attendee) => attendee.email)
|
|
|
|
|
: [];
|
|
|
|
|
|
2022-01-10 23:25:06 +00:00
|
|
|
|
const defaultValues = () => {
|
|
|
|
|
if (!rescheduleUid) {
|
|
|
|
|
return {
|
2022-03-15 14:39:20 +00:00
|
|
|
|
name: loggedInIsOwner ? "" : session?.user?.name || (router.query.name as string) || "",
|
|
|
|
|
email: loggedInIsOwner ? "" : session?.user?.email || (router.query.email as string) || "",
|
2022-01-10 23:25:06 +00:00
|
|
|
|
notes: (router.query.notes as string) || "",
|
|
|
|
|
guests: ensureArray(router.query.guest) as string[],
|
2022-03-15 14:39:20 +00:00
|
|
|
|
customInputs: eventType.customInputs.reduce(
|
2022-01-10 23:25:06 +00:00
|
|
|
|
(customInputs, input) => ({
|
|
|
|
|
...customInputs,
|
|
|
|
|
[input.id]: router.query[slugify(input.label)],
|
|
|
|
|
}),
|
|
|
|
|
{}
|
|
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
}
|
2022-03-15 14:39:20 +00:00
|
|
|
|
if (!booking || !booking.attendees.length) {
|
2022-01-10 23:25:06 +00:00
|
|
|
|
return {};
|
|
|
|
|
}
|
2022-03-15 14:39:20 +00:00
|
|
|
|
const primaryAttendee = booking.attendees[0];
|
2022-01-10 23:25:06 +00:00
|
|
|
|
if (!primaryAttendee) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
name: primaryAttendee.name || "",
|
|
|
|
|
email: primaryAttendee.email || "",
|
2022-04-14 21:25:24 +00:00
|
|
|
|
guests: guestListEmails,
|
|
|
|
|
notes: booking.description || "",
|
2022-01-10 23:25:06 +00:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2022-04-27 21:21:18 +00:00
|
|
|
|
const bookingFormSchema = z
|
|
|
|
|
.object({
|
|
|
|
|
name: z.string().min(1),
|
|
|
|
|
email: z.string().email(),
|
|
|
|
|
})
|
|
|
|
|
.passthrough();
|
2022-04-27 15:19:04 +00:00
|
|
|
|
|
2021-12-03 10:15:20 +00:00
|
|
|
|
const bookingForm = useForm<BookingFormValues>({
|
2022-01-10 23:25:06 +00:00
|
|
|
|
defaultValues: defaultValues(),
|
2022-04-27 21:21:18 +00:00
|
|
|
|
resolver: zodResolver(bookingFormSchema), // Since this isn't set to strict we only validate the fields in the schema
|
2021-12-03 10:15:20 +00:00
|
|
|
|
});
|
2021-09-14 08:45:28 +00:00
|
|
|
|
|
2021-12-03 10:15:20 +00:00
|
|
|
|
const selectedLocation = useWatch({
|
|
|
|
|
control: bookingForm.control,
|
|
|
|
|
name: "locationType",
|
|
|
|
|
defaultValue: ((): LocationType | undefined => {
|
|
|
|
|
if (router.query.location) {
|
|
|
|
|
return router.query.location as LocationType;
|
2021-09-14 08:45:28 +00:00
|
|
|
|
}
|
2021-12-03 10:15:20 +00:00
|
|
|
|
if (locations.length === 1) {
|
|
|
|
|
return locations[0]?.type;
|
|
|
|
|
}
|
|
|
|
|
})(),
|
|
|
|
|
});
|
2021-09-14 08:45:28 +00:00
|
|
|
|
|
2021-12-03 10:15:20 +00:00
|
|
|
|
const getLocationValue = (booking: Pick<BookingFormValues, "locationType" | "phone">) => {
|
|
|
|
|
const { locationType } = booking;
|
|
|
|
|
switch (locationType) {
|
|
|
|
|
case LocationType.Phone: {
|
2021-12-21 00:59:06 +00:00
|
|
|
|
return booking.phone || "";
|
2021-12-03 10:15:20 +00:00
|
|
|
|
}
|
|
|
|
|
case LocationType.InPerson: {
|
2021-12-21 00:59:06 +00:00
|
|
|
|
return locationInfo(locationType)?.address || "";
|
2021-12-03 10:15:20 +00:00
|
|
|
|
}
|
2022-03-31 19:57:12 +00:00
|
|
|
|
case LocationType.Link: {
|
|
|
|
|
return locationInfo(locationType)?.link || "";
|
|
|
|
|
}
|
2021-12-03 10:15:20 +00:00
|
|
|
|
// Catches all other location types, such as Google Meet, Zoom etc.
|
|
|
|
|
default:
|
2021-12-21 00:59:06 +00:00
|
|
|
|
return selectedLocation || "";
|
2021-12-03 10:15:20 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
2021-09-22 18:36:13 +00:00
|
|
|
|
|
2021-12-03 10:15:20 +00:00
|
|
|
|
const bookEvent = (booking: BookingFormValues) => {
|
|
|
|
|
telemetry.withJitsu((jitsu) =>
|
2022-04-14 02:47:34 +00:00
|
|
|
|
jitsu.track(
|
|
|
|
|
telemetryEventTypes.bookingConfirmed,
|
|
|
|
|
collectPageParameters("/book", { isTeamBooking: document.URL.includes("team/") })
|
|
|
|
|
)
|
2021-12-03 10:15:20 +00:00
|
|
|
|
);
|
2021-09-22 18:36:13 +00:00
|
|
|
|
|
2021-12-03 10:15:20 +00:00
|
|
|
|
// "metadata" is a reserved key to allow for connecting external users without relying on the email address.
|
|
|
|
|
// <...url>&metadata[user_id]=123 will be send as a custom input field as the hidden type.
|
2022-02-01 21:48:40 +00:00
|
|
|
|
|
|
|
|
|
// @TODO: move to metadata
|
2021-12-03 10:15:20 +00:00
|
|
|
|
const metadata = Object.keys(router.query)
|
|
|
|
|
.filter((key) => key.startsWith("metadata"))
|
|
|
|
|
.reduce(
|
|
|
|
|
(metadata, key) => ({
|
|
|
|
|
...metadata,
|
|
|
|
|
[key.substring("metadata[".length, key.length - 1)]: router.query[key],
|
|
|
|
|
}),
|
|
|
|
|
{}
|
|
|
|
|
);
|
2021-09-14 08:45:28 +00:00
|
|
|
|
|
2022-02-01 21:48:40 +00:00
|
|
|
|
let web3Details;
|
|
|
|
|
if (eventTypeDetail.metadata.smartContractAddress) {
|
|
|
|
|
web3Details = {
|
2022-02-03 20:08:25 +00:00
|
|
|
|
// @ts-ignore
|
|
|
|
|
userWallet: window.web3.currentProvider.selectedAddress,
|
2022-02-01 21:48:40 +00:00
|
|
|
|
userSignature: contracts[(eventTypeDetail.metadata.smartContractAddress || null) as number],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-03 10:15:20 +00:00
|
|
|
|
mutation.mutate({
|
|
|
|
|
...booking,
|
2022-02-01 21:48:40 +00:00
|
|
|
|
web3Details,
|
2021-12-03 10:15:20 +00:00
|
|
|
|
start: dayjs(date).format(),
|
2022-03-15 14:39:20 +00:00
|
|
|
|
end: dayjs(date).add(eventType.length, "minute").format(),
|
|
|
|
|
eventTypeId: eventType.id,
|
2022-04-06 17:20:30 +00:00
|
|
|
|
eventTypeSlug: eventType.slug,
|
2021-12-03 10:15:20 +00:00
|
|
|
|
timeZone: timeZone(),
|
|
|
|
|
language: i18n.language,
|
|
|
|
|
rescheduleUid,
|
2021-12-03 16:18:31 +00:00
|
|
|
|
user: router.query.user,
|
2022-03-03 09:57:59 +00:00
|
|
|
|
location: getLocationValue(
|
|
|
|
|
booking.locationType ? booking : { ...booking, locationType: selectedLocation }
|
|
|
|
|
),
|
2021-12-03 10:15:20 +00:00
|
|
|
|
metadata,
|
|
|
|
|
customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({
|
2022-03-15 14:39:20 +00:00
|
|
|
|
label: eventType.customInputs.find((input) => input.id === parseInt(inputId))!.label,
|
2021-12-03 10:15:20 +00:00
|
|
|
|
value: booking.customInputs![inputId],
|
|
|
|
|
})),
|
|
|
|
|
});
|
2021-09-14 08:45:28 +00:00
|
|
|
|
};
|
|
|
|
|
|
2022-04-14 21:25:24 +00:00
|
|
|
|
const disableInput = !!rescheduleUid;
|
|
|
|
|
|
2021-09-14 08:45:28 +00:00
|
|
|
|
return (
|
2021-09-24 22:11:30 +00:00
|
|
|
|
<div>
|
2022-02-10 11:07:14 +00:00
|
|
|
|
<Theme />
|
2021-09-24 22:11:30 +00:00
|
|
|
|
<Head>
|
|
|
|
|
<title>
|
2021-10-08 11:43:48 +00:00
|
|
|
|
{rescheduleUid
|
|
|
|
|
? t("booking_reschedule_confirmation", {
|
2022-03-15 14:39:20 +00:00
|
|
|
|
eventTypeTitle: eventType.title,
|
|
|
|
|
profileName: profile.name,
|
2021-10-08 11:43:48 +00:00
|
|
|
|
})
|
|
|
|
|
: t("booking_confirmation", {
|
2022-03-15 14:39:20 +00:00
|
|
|
|
eventTypeTitle: eventType.title,
|
|
|
|
|
profileName: profile.name,
|
2021-10-08 11:43:48 +00:00
|
|
|
|
})}{" "}
|
|
|
|
|
| Cal.com
|
2021-09-24 22:11:30 +00:00
|
|
|
|
</title>
|
|
|
|
|
<link rel="icon" href="/favicon.ico" />
|
|
|
|
|
</Head>
|
2022-03-15 14:39:20 +00:00
|
|
|
|
<CustomBranding lightVal={profile.brandColor} darkVal={profile.darkBrandColor} />
|
2022-04-08 05:33:24 +00:00
|
|
|
|
<main
|
2022-04-14 02:47:34 +00:00
|
|
|
|
className={classNames(
|
2022-04-25 04:33:00 +00:00
|
|
|
|
shouldAlignCentrally ? "mx-auto" : "",
|
|
|
|
|
isEmbed ? "" : "sm:my-24",
|
|
|
|
|
"my-0 max-w-3xl "
|
2022-04-14 02:47:34 +00:00
|
|
|
|
)}>
|
2021-09-24 22:11:30 +00:00
|
|
|
|
{isReady && (
|
2022-04-08 05:33:24 +00:00
|
|
|
|
<div
|
|
|
|
|
className={classNames(
|
2022-04-25 04:33:00 +00:00
|
|
|
|
"main overflow-hidden",
|
2022-04-08 05:33:24 +00:00
|
|
|
|
isEmbed ? "" : "border border-gray-200",
|
2022-04-25 04:33:00 +00:00
|
|
|
|
isBackgroundTransparent ? "" : "dark:border-1 bg-white dark:bg-gray-800",
|
|
|
|
|
"rounded-md sm:border sm:dark:border-gray-600"
|
2022-04-08 05:33:24 +00:00
|
|
|
|
)}>
|
2021-09-27 17:59:50 +00:00
|
|
|
|
<div className="px-4 py-5 sm:flex sm:p-4">
|
2022-03-26 23:40:41 +00:00
|
|
|
|
<div className="sm:w-1/2 sm:border-r sm:dark:border-gray-700">
|
2021-09-14 08:45:28 +00:00
|
|
|
|
<AvatarGroup
|
2022-03-24 13:15:24 +00:00
|
|
|
|
border="border-2 border-white dark:border-gray-800"
|
2021-11-08 23:06:50 +00:00
|
|
|
|
size={14}
|
2022-03-15 14:39:20 +00:00
|
|
|
|
items={[{ image: profile.image || "", alt: profile.name || "" }].concat(
|
|
|
|
|
eventType.users
|
|
|
|
|
.filter((user) => user.name !== profile.name)
|
2021-09-14 08:45:28 +00:00
|
|
|
|
.map((user) => ({
|
2021-12-21 00:59:06 +00:00
|
|
|
|
image: user.avatar || "",
|
|
|
|
|
alt: user.name || "",
|
2021-09-14 08:45:28 +00:00
|
|
|
|
}))
|
|
|
|
|
)}
|
|
|
|
|
/>
|
2022-04-08 05:33:24 +00:00
|
|
|
|
<h2 className="font-cal text-bookinglight mt-2 font-medium dark:text-gray-300">
|
|
|
|
|
{profile.name}
|
|
|
|
|
</h2>
|
2022-04-14 21:25:24 +00:00
|
|
|
|
<h1 className="text-bookingdark mb-4 text-xl font-semibold dark:text-white">
|
2022-03-15 14:39:20 +00:00
|
|
|
|
{eventType.title}
|
2021-09-14 08:45:28 +00:00
|
|
|
|
</h1>
|
2022-04-14 21:25:24 +00:00
|
|
|
|
{eventType?.description && (
|
|
|
|
|
<p className="text-bookinglight mb-2 dark:text-white">
|
|
|
|
|
<InformationCircleIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
|
|
|
|
|
{eventType.description}
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
<p className="text-bookinglight mb-2 dark:text-white">
|
|
|
|
|
<ClockIcon className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4 text-gray-400" />
|
2022-03-15 14:39:20 +00:00
|
|
|
|
{eventType.length} {t("minutes")}
|
2021-09-14 08:45:28 +00:00
|
|
|
|
</p>
|
2022-03-15 14:39:20 +00:00
|
|
|
|
{eventType.price > 0 && (
|
2022-04-14 21:25:24 +00:00
|
|
|
|
<p className="text-bookinglight mb-1 -ml-2 px-2 py-1 dark:text-white">
|
|
|
|
|
<CreditCardIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
|
2021-09-22 18:36:13 +00:00
|
|
|
|
<IntlProvider locale="en">
|
|
|
|
|
<FormattedNumber
|
2022-03-15 14:39:20 +00:00
|
|
|
|
value={eventType.price / 100.0}
|
2021-09-22 18:36:13 +00:00
|
|
|
|
style="currency"
|
2022-03-15 14:39:20 +00:00
|
|
|
|
currency={eventType.currency.toUpperCase()}
|
2021-09-22 18:36:13 +00:00
|
|
|
|
/>
|
|
|
|
|
</IntlProvider>
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
2022-04-08 05:33:24 +00:00
|
|
|
|
<p className="text-bookinghighlight mb-4">
|
2022-04-14 21:25:24 +00:00
|
|
|
|
<CalendarIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
|
|
|
|
|
{parseDate(date, i18n)}
|
2021-09-14 08:45:28 +00:00
|
|
|
|
</p>
|
2022-02-01 21:48:40 +00:00
|
|
|
|
{eventTypeDetail.isWeb3Active && eventType.metadata.smartContractAddress && (
|
2022-04-08 05:33:24 +00:00
|
|
|
|
<p className="text-bookinglight mb-1 -ml-2 px-2 py-1">
|
2022-02-14 20:20:10 +00:00
|
|
|
|
{t("requires_ownership_of_a_token") + " " + eventType.metadata.smartContractAddress}
|
2022-02-01 21:48:40 +00:00
|
|
|
|
</p>
|
|
|
|
|
)}
|
2022-04-14 21:25:24 +00:00
|
|
|
|
{booking?.startTime && rescheduleUid && (
|
|
|
|
|
<div>
|
|
|
|
|
<p className="mt-8 mb-2 text-gray-600 dark:text-white" data-testid="former_time_p">
|
|
|
|
|
{t("former_time")}
|
|
|
|
|
</p>
|
|
|
|
|
<p className="text-gray-500 line-through dark:text-white">
|
|
|
|
|
<CalendarIcon className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4 text-gray-400" />
|
|
|
|
|
{typeof booking.startTime === "string" && parseDate(dayjs(booking.startTime), i18n)}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2021-09-14 08:45:28 +00:00
|
|
|
|
</div>
|
2022-04-17 11:25:11 +00:00
|
|
|
|
<div className="mt-8 sm:w-1/2 sm:pl-8 sm:pr-4">
|
2021-12-03 10:15:20 +00:00
|
|
|
|
<Form form={bookingForm} handleSubmit={bookEvent}>
|
2021-09-14 08:45:28 +00:00
|
|
|
|
<div className="mb-4">
|
2021-10-12 08:29:12 +00:00
|
|
|
|
<label htmlFor="name" className="block text-sm font-medium text-gray-700 dark:text-white">
|
2021-10-08 11:43:48 +00:00
|
|
|
|
{t("your_name")}
|
2021-09-14 08:45:28 +00:00
|
|
|
|
</label>
|
|
|
|
|
<div className="mt-1">
|
|
|
|
|
<input
|
2022-04-27 15:19:04 +00:00
|
|
|
|
{...bookingForm.register("name", { required: true })}
|
2021-09-14 08:45:28 +00:00
|
|
|
|
type="text"
|
|
|
|
|
name="name"
|
|
|
|
|
id="name"
|
|
|
|
|
required
|
2022-04-14 21:25:24 +00:00
|
|
|
|
className={classNames(
|
|
|
|
|
"focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-gray-700 dark:text-white dark:selection:bg-green-500 sm:text-sm",
|
|
|
|
|
disableInput ? "bg-gray-200 dark:text-gray-500" : ""
|
|
|
|
|
)}
|
2022-02-14 20:20:10 +00:00
|
|
|
|
placeholder={t("example_name")}
|
2022-04-14 21:25:24 +00:00
|
|
|
|
disabled={disableInput}
|
2021-09-14 08:45:28 +00:00
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="mb-4">
|
|
|
|
|
<label
|
|
|
|
|
htmlFor="email"
|
2021-10-12 08:29:12 +00:00
|
|
|
|
className="block text-sm font-medium text-gray-700 dark:text-white">
|
2021-10-08 11:43:48 +00:00
|
|
|
|
{t("email_address")}
|
2021-09-14 08:45:28 +00:00
|
|
|
|
</label>
|
|
|
|
|
<div className="mt-1">
|
2021-12-21 00:59:06 +00:00
|
|
|
|
<EmailInput
|
2021-12-03 10:15:20 +00:00
|
|
|
|
{...bookingForm.register("email")}
|
2021-09-14 08:45:28 +00:00
|
|
|
|
required
|
2022-04-14 21:25:24 +00:00
|
|
|
|
className={classNames(
|
|
|
|
|
"focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-gray-700 dark:text-white dark:selection:bg-green-500 sm:text-sm",
|
|
|
|
|
disableInput ? "bg-gray-200 dark:text-gray-500" : ""
|
|
|
|
|
)}
|
2021-09-14 08:45:28 +00:00
|
|
|
|
placeholder="you@example.com"
|
2022-03-24 02:27:35 +00:00
|
|
|
|
type="search" // Disables annoying 1password intrusive popup (non-optimal, I know I know...)
|
2022-04-14 21:25:24 +00:00
|
|
|
|
disabled={disableInput}
|
2021-09-14 08:45:28 +00:00
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{locations.length > 1 && (
|
|
|
|
|
<div className="mb-4">
|
2021-09-27 17:59:50 +00:00
|
|
|
|
<span className="block text-sm font-medium text-gray-700 dark:text-white">
|
2021-10-08 11:43:48 +00:00
|
|
|
|
{t("location")}
|
2021-09-14 08:45:28 +00:00
|
|
|
|
</span>
|
2021-12-03 10:15:20 +00:00
|
|
|
|
{locations.map((location, i) => (
|
|
|
|
|
<label key={i} className="block">
|
2021-09-14 08:45:28 +00:00
|
|
|
|
<input
|
|
|
|
|
type="radio"
|
2022-02-09 00:05:13 +00:00
|
|
|
|
className="location h-4 w-4 border-gray-300 text-black focus:ring-black ltr:mr-2 rtl:ml-2"
|
2021-12-03 10:15:20 +00:00
|
|
|
|
{...bookingForm.register("locationType", { required: true })}
|
2021-09-14 08:45:28 +00:00
|
|
|
|
value={location.type}
|
2021-12-03 10:15:20 +00:00
|
|
|
|
defaultChecked={selectedLocation === location.type}
|
2021-09-14 08:45:28 +00:00
|
|
|
|
/>
|
2022-02-09 23:10:43 +00:00
|
|
|
|
<span className="text-sm ltr:ml-2 rtl:mr-2 dark:text-gray-500">
|
2021-09-14 08:45:28 +00:00
|
|
|
|
{locationLabels[location.type]}
|
|
|
|
|
</span>
|
|
|
|
|
</label>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
{selectedLocation === LocationType.Phone && (
|
|
|
|
|
<div className="mb-4">
|
|
|
|
|
<label
|
|
|
|
|
htmlFor="phone"
|
2021-09-27 17:59:50 +00:00
|
|
|
|
className="block text-sm font-medium text-gray-700 dark:text-white">
|
2021-10-08 11:43:48 +00:00
|
|
|
|
{t("phone_number")}
|
2021-09-14 08:45:28 +00:00
|
|
|
|
</label>
|
|
|
|
|
<div className="mt-1">
|
2022-04-06 12:37:06 +00:00
|
|
|
|
<PhoneInput<BookingFormValues>
|
2022-03-03 09:57:59 +00:00
|
|
|
|
control={bookingForm.control}
|
|
|
|
|
name="phone"
|
|
|
|
|
placeholder={t("enter_phone_number")}
|
|
|
|
|
id="phone"
|
|
|
|
|
required
|
2022-04-14 21:25:24 +00:00
|
|
|
|
disabled={disableInput}
|
2022-03-03 09:57:59 +00:00
|
|
|
|
/>
|
2021-09-14 08:45:28 +00:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2022-03-15 14:39:20 +00:00
|
|
|
|
{eventType.customInputs
|
2021-12-03 10:15:20 +00:00
|
|
|
|
.sort((a, b) => a.id - b.id)
|
|
|
|
|
.map((input) => (
|
|
|
|
|
<div className="mb-4" key={input.id}>
|
|
|
|
|
{input.type !== EventTypeCustomInputType.BOOL && (
|
|
|
|
|
<label
|
|
|
|
|
htmlFor={"custom_" + input.id}
|
2022-02-09 00:05:13 +00:00
|
|
|
|
className="mb-1 block text-sm font-medium text-gray-700 dark:text-white">
|
2021-12-03 10:15:20 +00:00
|
|
|
|
{input.label}
|
|
|
|
|
</label>
|
|
|
|
|
)}
|
|
|
|
|
{input.type === EventTypeCustomInputType.TEXTLONG && (
|
|
|
|
|
<textarea
|
|
|
|
|
{...bookingForm.register(`customInputs.${input.id}`, {
|
|
|
|
|
required: input.required,
|
|
|
|
|
})}
|
|
|
|
|
id={"custom_" + input.id}
|
|
|
|
|
rows={3}
|
2022-04-14 21:25:24 +00:00
|
|
|
|
className={classNames(
|
|
|
|
|
"focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-gray-700 dark:text-white dark:selection:bg-green-500 sm:text-sm",
|
|
|
|
|
disableInput ? "bg-gray-200 dark:text-gray-500" : ""
|
|
|
|
|
)}
|
2021-12-03 10:15:20 +00:00
|
|
|
|
placeholder={input.placeholder}
|
2022-04-14 21:25:24 +00:00
|
|
|
|
disabled={disableInput}
|
2021-12-03 10:15:20 +00:00
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
{input.type === EventTypeCustomInputType.TEXT && (
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
{...bookingForm.register(`customInputs.${input.id}`, {
|
|
|
|
|
required: input.required,
|
|
|
|
|
})}
|
|
|
|
|
id={"custom_" + input.id}
|
2022-03-26 23:40:41 +00:00
|
|
|
|
className="focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-gray-700 dark:text-white dark:selection:bg-green-500 sm:text-sm"
|
2021-12-03 10:15:20 +00:00
|
|
|
|
placeholder={input.placeholder}
|
2022-04-14 21:25:24 +00:00
|
|
|
|
disabled={disableInput}
|
2021-12-03 10:15:20 +00:00
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
{input.type === EventTypeCustomInputType.NUMBER && (
|
|
|
|
|
<input
|
|
|
|
|
type="number"
|
|
|
|
|
{...bookingForm.register(`customInputs.${input.id}`, {
|
|
|
|
|
required: input.required,
|
|
|
|
|
})}
|
|
|
|
|
id={"custom_" + input.id}
|
2022-03-26 23:40:41 +00:00
|
|
|
|
className="focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-gray-700 dark:text-white dark:selection:bg-green-500 sm:text-sm"
|
2021-12-03 10:15:20 +00:00
|
|
|
|
placeholder=""
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
{input.type === EventTypeCustomInputType.BOOL && (
|
2022-02-09 00:05:13 +00:00
|
|
|
|
<div className="flex h-5 items-center">
|
2021-12-03 10:15:20 +00:00
|
|
|
|
<input
|
|
|
|
|
type="checkbox"
|
|
|
|
|
{...bookingForm.register(`customInputs.${input.id}`, {
|
|
|
|
|
required: input.required,
|
|
|
|
|
})}
|
|
|
|
|
id={"custom_" + input.id}
|
2022-02-09 00:05:13 +00:00
|
|
|
|
className="h-4 w-4 rounded border-gray-300 text-black focus:ring-black ltr:mr-2 rtl:ml-2"
|
2021-12-03 10:15:20 +00:00
|
|
|
|
placeholder=""
|
|
|
|
|
/>
|
2021-09-14 08:45:28 +00:00
|
|
|
|
<label
|
|
|
|
|
htmlFor={"custom_" + input.id}
|
2022-02-09 00:05:13 +00:00
|
|
|
|
className="mb-1 block text-sm font-medium text-gray-700 dark:text-white">
|
2021-09-14 08:45:28 +00:00
|
|
|
|
{input.label}
|
|
|
|
|
</label>
|
2021-12-03 10:15:20 +00:00
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
2022-03-15 14:39:20 +00:00
|
|
|
|
{!eventType.disableGuests && (
|
2021-09-22 11:04:32 +00:00
|
|
|
|
<div className="mb-4">
|
|
|
|
|
{!guestToggle && (
|
2021-09-14 08:45:28 +00:00
|
|
|
|
<label
|
2021-12-03 10:15:20 +00:00
|
|
|
|
onClick={() => setGuestToggle(!guestToggle)}
|
2021-09-14 08:45:28 +00:00
|
|
|
|
htmlFor="guests"
|
2022-02-09 00:05:13 +00:00
|
|
|
|
className="mb-1 block text-sm font-medium hover:cursor-pointer dark:text-white">
|
2021-12-03 10:15:20 +00:00
|
|
|
|
{/*<UserAddIcon className="inline-block w-5 h-5 mr-1 -mt-1" />*/}
|
2021-10-08 11:43:48 +00:00
|
|
|
|
{t("additional_guests")}
|
2021-09-14 08:45:28 +00:00
|
|
|
|
</label>
|
2021-09-22 11:04:32 +00:00
|
|
|
|
)}
|
|
|
|
|
{guestToggle && (
|
|
|
|
|
<div>
|
|
|
|
|
<label
|
|
|
|
|
htmlFor="guests"
|
2022-02-09 00:05:13 +00:00
|
|
|
|
className="mb-1 block text-sm font-medium text-gray-700 dark:text-white">
|
2021-10-15 10:53:42 +00:00
|
|
|
|
{t("guests")}
|
2021-09-22 11:04:32 +00:00
|
|
|
|
</label>
|
2022-04-14 21:25:24 +00:00
|
|
|
|
{!disableInput && (
|
|
|
|
|
<Controller
|
|
|
|
|
control={bookingForm.control}
|
|
|
|
|
name="guests"
|
|
|
|
|
render={({ field: { onChange, value } }) => (
|
|
|
|
|
<ReactMultiEmail
|
|
|
|
|
className="relative"
|
|
|
|
|
placeholder="guest@example.com"
|
|
|
|
|
emails={value}
|
|
|
|
|
onChange={onChange}
|
|
|
|
|
getLabel={(
|
|
|
|
|
email: string,
|
|
|
|
|
index: number,
|
|
|
|
|
removeEmail: (index: number) => void
|
|
|
|
|
) => {
|
|
|
|
|
return (
|
|
|
|
|
<div data-tag key={index} className="cursor-pointer">
|
|
|
|
|
{email}
|
|
|
|
|
{!disableInput && (
|
|
|
|
|
<span data-tag-handle onClick={() => removeEmail(index)}>
|
|
|
|
|
×
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
{/* Custom code when guest emails should not be editable */}
|
|
|
|
|
{disableInput && guestListEmails && guestListEmails.length > 0 && (
|
|
|
|
|
<div data-tag className="react-multi-email">
|
|
|
|
|
{/* // @TODO: user owners are appearing as guest here when should be only user input */}
|
|
|
|
|
{guestListEmails.map((email, index) => {
|
|
|
|
|
return (
|
|
|
|
|
<div key={index} className="cursor-pointer">
|
|
|
|
|
<span data-tag>{email}</span>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2021-09-22 11:04:32 +00:00
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2021-09-14 08:45:28 +00:00
|
|
|
|
<div className="mb-4">
|
|
|
|
|
<label
|
|
|
|
|
htmlFor="notes"
|
2022-02-09 00:05:13 +00:00
|
|
|
|
className="mb-1 block text-sm font-medium text-gray-700 dark:text-white">
|
2021-10-08 11:43:48 +00:00
|
|
|
|
{t("additional_notes")}
|
2021-09-14 08:45:28 +00:00
|
|
|
|
</label>
|
|
|
|
|
<textarea
|
2021-12-03 10:15:20 +00:00
|
|
|
|
{...bookingForm.register("notes")}
|
2021-09-14 08:45:28 +00:00
|
|
|
|
id="notes"
|
2022-04-14 21:25:24 +00:00
|
|
|
|
name="notes"
|
2021-09-14 08:45:28 +00:00
|
|
|
|
rows={3}
|
2022-04-14 21:25:24 +00:00
|
|
|
|
className={classNames(
|
|
|
|
|
"focus:border-brand block w-full rounded-sm border-gray-300 shadow-sm focus:ring-black dark:border-gray-900 dark:bg-gray-700 dark:text-white dark:selection:bg-green-500 sm:text-sm",
|
|
|
|
|
disableInput ? "bg-gray-200 dark:text-gray-500" : ""
|
|
|
|
|
)}
|
2021-10-08 11:43:48 +00:00
|
|
|
|
placeholder={t("share_additional_notes")}
|
2022-04-14 21:25:24 +00:00
|
|
|
|
disabled={disableInput}
|
2021-09-14 08:45:28 +00:00
|
|
|
|
/>
|
|
|
|
|
</div>
|
2022-02-03 20:08:25 +00:00
|
|
|
|
<div className="flex items-start space-x-2 rtl:space-x-reverse">
|
2022-03-24 17:32:28 +00:00
|
|
|
|
<Button
|
|
|
|
|
type="submit"
|
|
|
|
|
data-testid={rescheduleUid ? "confirm-reschedule-button" : "confirm-book-button"}
|
|
|
|
|
loading={mutation.isLoading}>
|
2021-10-08 11:43:48 +00:00
|
|
|
|
{rescheduleUid ? t("reschedule") : t("confirm")}
|
2021-09-14 08:45:28 +00:00
|
|
|
|
</Button>
|
|
|
|
|
<Button color="secondary" type="button" onClick={() => router.back()}>
|
2021-10-08 11:43:48 +00:00
|
|
|
|
{t("cancel")}
|
2021-09-14 08:45:28 +00:00
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
2021-12-03 10:15:20 +00:00
|
|
|
|
</Form>
|
|
|
|
|
{mutation.isError && (
|
2022-03-07 17:55:24 +00:00
|
|
|
|
<div
|
|
|
|
|
data-testid="booking-fail"
|
|
|
|
|
className="mt-2 border-l-4 border-yellow-400 bg-yellow-50 p-4">
|
2021-09-14 08:45:28 +00:00
|
|
|
|
<div className="flex">
|
|
|
|
|
<div className="flex-shrink-0">
|
2022-02-09 00:05:13 +00:00
|
|
|
|
<ExclamationIcon className="h-5 w-5 text-yellow-400" aria-hidden="true" />
|
2021-09-14 08:45:28 +00:00
|
|
|
|
</div>
|
2022-02-01 22:17:37 +00:00
|
|
|
|
<div className="ltr:ml-3 rtl:mr-3">
|
2021-09-14 08:45:28 +00:00
|
|
|
|
<p className="text-sm text-yellow-700">
|
2022-03-24 02:27:35 +00:00
|
|
|
|
{rescheduleUid ? t("reschedule_fail") : t("booking_fail")}{" "}
|
|
|
|
|
{(mutation.error as HttpError)?.message}
|
2021-09-14 08:45:28 +00:00
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2021-09-24 22:11:30 +00:00
|
|
|
|
)}
|
|
|
|
|
</main>
|
|
|
|
|
</div>
|
2021-09-14 08:45:28 +00:00
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default BookingPage;
|