2021-09-14 08:45:28 +00:00
|
|
|
// Get router variables
|
2022-10-10 13:24:06 +00:00
|
|
|
import autoAnimate from "@formkit/auto-animate";
|
2022-06-15 20:54:31 +00:00
|
|
|
import { EventType } from "@prisma/client";
|
2022-10-05 13:11:26 +00:00
|
|
|
import * as Popover from "@radix-ui/react-popover";
|
2022-05-25 20:34:08 +00:00
|
|
|
import { TFunction } from "next-i18next";
|
2021-09-22 19:52:38 +00:00
|
|
|
import { useRouter } from "next/router";
|
2022-10-10 13:24:06 +00:00
|
|
|
import { useReducer, useEffect, useMemo, useState, useRef } from "react";
|
2022-09-05 21:10:58 +00:00
|
|
|
import { Toaster } from "react-hot-toast";
|
2021-09-22 19:52:38 +00:00
|
|
|
import { FormattedNumber, IntlProvider } from "react-intl";
|
2022-06-27 21:01:46 +00:00
|
|
|
import { z } from "zod";
|
2021-09-22 19:52:38 +00:00
|
|
|
|
2022-10-14 16:24:43 +00:00
|
|
|
import BookingPageTagManager from "@calcom/app-store/BookingPageTagManager";
|
|
|
|
import { getEventTypeAppData } from "@calcom/app-store/utils";
|
2022-06-28 20:40:58 +00:00
|
|
|
import dayjs, { Dayjs } from "@calcom/dayjs";
|
2022-04-25 04:33:00 +00:00
|
|
|
import {
|
2022-07-23 00:39:50 +00:00
|
|
|
useEmbedNonStylesConfig,
|
2022-08-05 18:44:30 +00:00
|
|
|
useEmbedStyles,
|
2022-04-25 04:33:00 +00:00
|
|
|
useIsBackgroundTransparent,
|
2022-08-05 18:44:30 +00:00
|
|
|
useIsEmbed,
|
2022-05-27 15:37:02 +00:00
|
|
|
} from "@calcom/embed-core/embed-iframe";
|
2022-07-23 00:39:50 +00:00
|
|
|
import CustomBranding from "@calcom/lib/CustomBranding";
|
2022-04-08 05:33:24 +00:00
|
|
|
import classNames from "@calcom/lib/classNames";
|
2022-08-15 19:52:01 +00:00
|
|
|
import { WEBSITE_URL } from "@calcom/lib/constants";
|
2022-10-14 16:24:43 +00:00
|
|
|
import getStripeAppData from "@calcom/lib/getStripeAppData";
|
2022-04-14 21:25:24 +00:00
|
|
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
2022-07-28 19:58:26 +00:00
|
|
|
import useTheme from "@calcom/lib/hooks/useTheme";
|
2022-08-12 19:29:29 +00:00
|
|
|
import notEmpty from "@calcom/lib/notEmpty";
|
2022-06-10 20:38:06 +00:00
|
|
|
import { getRecurringFreq } from "@calcom/lib/recurringStrings";
|
2022-07-28 19:58:26 +00:00
|
|
|
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
2022-09-30 12:45:28 +00:00
|
|
|
import { detectBrowserTimeFormat, getIs24hClockFromLocalStorage } from "@calcom/lib/timeFormat";
|
2022-07-22 17:27:06 +00:00
|
|
|
import { trpc } from "@calcom/trpc/react";
|
2022-07-27 02:24:00 +00:00
|
|
|
import { Icon } from "@calcom/ui/Icon";
|
2022-08-24 20:18:42 +00:00
|
|
|
import DatePicker from "@calcom/ui/v2/modules/booker/DatePicker";
|
2022-04-08 05:33:24 +00:00
|
|
|
|
2022-06-27 21:01:46 +00:00
|
|
|
import { timeZone as localStorageTimeZone } from "@lib/clock";
|
|
|
|
// import { timeZone } from "@lib/clock";
|
2022-04-04 15:44:04 +00:00
|
|
|
import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally";
|
2021-09-22 19:52:38 +00:00
|
|
|
import { isBrandingHidden } from "@lib/isBrandingHidden";
|
|
|
|
|
2022-09-05 21:10:58 +00:00
|
|
|
import Gates, { Gate, GateState } from "@components/Gates";
|
2021-09-14 08:45:28 +00:00
|
|
|
import AvailableTimes from "@components/booking/AvailableTimes";
|
2022-10-12 08:39:14 +00:00
|
|
|
import BookingDescription from "@components/booking/BookingDescription";
|
2021-09-14 08:45:28 +00:00
|
|
|
import TimeOptions from "@components/booking/TimeOptions";
|
|
|
|
import { HeadSeo } from "@components/seo/head-seo";
|
2021-09-26 14:04:01 +00:00
|
|
|
import PoweredByCal from "@components/ui/PoweredByCal";
|
2021-09-14 08:45:28 +00:00
|
|
|
|
2022-06-15 20:54:31 +00:00
|
|
|
import type { AvailabilityPageProps } from "../../../pages/[user]/[type]";
|
|
|
|
import type { DynamicAvailabilityPageProps } from "../../../pages/d/[link]/[slug]";
|
|
|
|
import type { AvailabilityTeamPageProps } from "../../../pages/team/[slug]/[type]";
|
2021-09-23 14:08:44 +00:00
|
|
|
|
2022-07-06 14:47:22 +00:00
|
|
|
const GoBackToPreviousPage = ({ t }: { t: TFunction }) => {
|
2021-09-14 08:45:28 +00:00
|
|
|
const router = useRouter();
|
2022-07-06 14:47:22 +00:00
|
|
|
const path = router.asPath.split("/");
|
|
|
|
path.pop(); // Remove the last item (where we currently are)
|
|
|
|
path.shift(); // Removes first item e.g. if we were visitng "/teams/test/30mins" the array will new look like ["teams","test"]
|
|
|
|
const slug = path.join("/");
|
2022-07-04 14:26:22 +00:00
|
|
|
return (
|
2022-06-15 20:54:31 +00:00
|
|
|
<div className="flex h-full flex-col justify-end">
|
2022-07-04 14:26:22 +00:00
|
|
|
<button title={t("profile")} onClick={() => router.replace(`${WEBSITE_URL}/${slug}`)}>
|
2022-08-24 20:18:42 +00:00
|
|
|
<Icon.FiArrowLeft className="dark:text-darkgray-600 h-4 w-4 text-black transition-opacity hover:cursor-pointer" />
|
2022-07-04 14:26:22 +00:00
|
|
|
<p className="sr-only">Go Back</p>
|
|
|
|
</button>
|
2022-06-15 20:54:31 +00:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const useSlots = ({
|
|
|
|
eventTypeId,
|
2022-08-12 19:29:29 +00:00
|
|
|
eventTypeSlug,
|
2022-06-15 20:54:31 +00:00
|
|
|
startTime,
|
|
|
|
endTime,
|
2022-08-12 19:29:29 +00:00
|
|
|
usernameList,
|
2022-06-27 21:01:46 +00:00
|
|
|
timeZone,
|
2022-06-15 20:54:31 +00:00
|
|
|
}: {
|
|
|
|
eventTypeId: number;
|
2022-08-12 19:29:29 +00:00
|
|
|
eventTypeSlug: string;
|
2022-06-27 21:01:46 +00:00
|
|
|
startTime?: Dayjs;
|
|
|
|
endTime?: Dayjs;
|
2022-08-12 19:29:29 +00:00
|
|
|
usernameList: string[];
|
2022-07-14 10:28:46 +00:00
|
|
|
timeZone?: string;
|
2022-06-15 20:54:31 +00:00
|
|
|
}) => {
|
2022-09-29 16:58:29 +00:00
|
|
|
const { data, isLoading, isPaused } = trpc.useQuery(
|
2022-06-19 15:02:00 +00:00
|
|
|
[
|
|
|
|
"viewer.public.slots.getSchedule",
|
|
|
|
{
|
|
|
|
eventTypeId,
|
2022-08-12 19:29:29 +00:00
|
|
|
eventTypeSlug,
|
|
|
|
usernameList,
|
2022-06-27 21:01:46 +00:00
|
|
|
startTime: startTime?.toISOString() || "",
|
|
|
|
endTime: endTime?.toISOString() || "",
|
2022-06-29 09:01:30 +00:00
|
|
|
timeZone,
|
2022-06-19 15:02:00 +00:00
|
|
|
},
|
|
|
|
],
|
2022-06-27 21:01:46 +00:00
|
|
|
{ enabled: !!startTime && !!endTime }
|
2022-06-19 15:02:00 +00:00
|
|
|
);
|
2022-06-27 21:01:46 +00:00
|
|
|
const [cachedSlots, setCachedSlots] = useState<NonNullable<typeof data>["slots"]>({});
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (data?.slots) {
|
|
|
|
setCachedSlots((c) => ({ ...c, ...data?.slots }));
|
|
|
|
}
|
|
|
|
}, [data]);
|
|
|
|
|
2022-09-29 16:58:29 +00:00
|
|
|
// The very first time isPaused is set if auto-fetch is disabled, so isPaused should also be considered a loading state.
|
|
|
|
return { slots: cachedSlots, isLoading: isLoading || isPaused };
|
2022-06-15 20:54:31 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const SlotPicker = ({
|
|
|
|
eventType,
|
|
|
|
timeFormat,
|
|
|
|
timeZone,
|
|
|
|
recurringEventCount,
|
2022-08-12 19:29:29 +00:00
|
|
|
users,
|
2022-06-15 20:54:31 +00:00
|
|
|
seatsPerTimeSlot,
|
|
|
|
weekStart = 0,
|
2022-09-05 21:10:58 +00:00
|
|
|
ethSignature,
|
2022-06-15 20:54:31 +00:00
|
|
|
}: {
|
|
|
|
eventType: Pick<EventType, "id" | "schedulingType" | "slug">;
|
|
|
|
timeFormat: string;
|
2022-07-14 10:28:46 +00:00
|
|
|
timeZone?: string;
|
2022-06-15 20:54:31 +00:00
|
|
|
seatsPerTimeSlot?: number;
|
|
|
|
recurringEventCount?: number;
|
2022-08-12 19:29:29 +00:00
|
|
|
users: string[];
|
2022-06-15 20:54:31 +00:00
|
|
|
weekStart?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
2022-09-05 21:10:58 +00:00
|
|
|
ethSignature?: string;
|
2022-06-15 20:54:31 +00:00
|
|
|
}) => {
|
2022-06-27 21:01:46 +00:00
|
|
|
const [selectedDate, setSelectedDate] = useState<Dayjs>();
|
|
|
|
const [browsingDate, setBrowsingDate] = useState<Dayjs>();
|
2022-06-28 02:17:23 +00:00
|
|
|
const { date, setQuery: setDate } = useRouterQuery("date");
|
|
|
|
const { month, setQuery: setMonth } = useRouterQuery("month");
|
2022-06-27 21:01:46 +00:00
|
|
|
const router = useRouter();
|
2022-10-10 13:24:06 +00:00
|
|
|
const slotPickerRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
slotPickerRef.current && autoAnimate(slotPickerRef.current);
|
|
|
|
}, [slotPickerRef]);
|
2022-06-15 20:54:31 +00:00
|
|
|
|
|
|
|
useEffect(() => {
|
2022-06-28 02:17:23 +00:00
|
|
|
if (!router.isReady) return;
|
2022-06-27 21:01:46 +00:00
|
|
|
|
|
|
|
// Etc/GMT is not actually a timeZone, so handle this select option explicitly to prevent a hard crash.
|
|
|
|
if (timeZone === "Etc/GMT") {
|
2022-07-25 21:45:34 +00:00
|
|
|
setBrowsingDate(dayjs.utc(month).set("date", 1).set("hour", 0).set("minute", 0).set("second", 0));
|
2022-06-27 21:01:46 +00:00
|
|
|
if (date) {
|
|
|
|
setSelectedDate(dayjs.utc(date));
|
|
|
|
}
|
|
|
|
} else {
|
2022-07-25 21:45:34 +00:00
|
|
|
// Set the start of the month without shifting time like startOf() may do.
|
|
|
|
setBrowsingDate(
|
|
|
|
dayjs.tz(month, timeZone).set("date", 1).set("hour", 0).set("minute", 0).set("second", 0)
|
|
|
|
);
|
2022-06-27 21:01:46 +00:00
|
|
|
if (date) {
|
2022-07-25 21:45:34 +00:00
|
|
|
// It's important to set the date immediately to the timeZone, dayjs(date) will convert to browsertime.
|
|
|
|
setSelectedDate(dayjs.tz(date, timeZone));
|
2022-06-27 21:01:46 +00:00
|
|
|
}
|
2022-02-03 23:23:20 +00:00
|
|
|
}
|
2022-06-28 02:17:23 +00:00
|
|
|
}, [router.isReady, month, date, timeZone]);
|
2022-06-15 20:54:31 +00:00
|
|
|
|
2022-06-27 21:01:46 +00:00
|
|
|
const { i18n, isLocaleReady } = useLocale();
|
|
|
|
const { slots: _1 } = useSlots({
|
|
|
|
eventTypeId: eventType.id,
|
2022-08-12 19:29:29 +00:00
|
|
|
eventTypeSlug: eventType.slug,
|
|
|
|
usernameList: users,
|
2022-06-28 22:53:14 +00:00
|
|
|
startTime: selectedDate?.startOf("day"),
|
|
|
|
endTime: selectedDate?.endOf("day"),
|
2022-06-27 21:01:46 +00:00
|
|
|
timeZone,
|
|
|
|
});
|
|
|
|
const { slots: _2, isLoading } = useSlots({
|
2022-06-15 20:54:31 +00:00
|
|
|
eventTypeId: eventType.id,
|
2022-08-12 19:29:29 +00:00
|
|
|
eventTypeSlug: eventType.slug,
|
|
|
|
usernameList: users,
|
2022-06-27 21:01:46 +00:00
|
|
|
startTime: browsingDate?.startOf("month"),
|
|
|
|
endTime: browsingDate?.endOf("month"),
|
|
|
|
timeZone,
|
2022-06-15 20:54:31 +00:00
|
|
|
});
|
2022-02-03 23:23:20 +00:00
|
|
|
|
2022-09-14 17:32:52 +00:00
|
|
|
const slots = useMemo(() => ({ ..._2, ..._1 }), [_1, _2]);
|
2022-06-27 21:01:46 +00:00
|
|
|
|
2022-06-15 20:54:31 +00:00
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<DatePicker
|
|
|
|
isLoading={isLoading}
|
2022-07-14 10:28:46 +00:00
|
|
|
className={classNames(
|
2022-10-10 13:24:06 +00:00
|
|
|
"mt-8 px-4 pb-4 sm:mt-0 md:min-w-[300px] md:px-5 lg:min-w-[455px]",
|
|
|
|
selectedDate ? "sm:dark:border-darkgray-200 border-gray-200 sm:border-r sm:p-4 sm:pr-6" : "sm:p-4"
|
2022-07-14 10:28:46 +00:00
|
|
|
)}
|
2022-06-15 20:54:31 +00:00
|
|
|
includedDates={Object.keys(slots).filter((k) => slots[k].length > 0)}
|
2022-06-19 15:02:00 +00:00
|
|
|
locale={isLocaleReady ? i18n.language : "en"}
|
2022-06-15 20:54:31 +00:00
|
|
|
selected={selectedDate}
|
2022-06-28 02:17:23 +00:00
|
|
|
onChange={(newDate) => {
|
|
|
|
setDate(newDate.format("YYYY-MM-DD"));
|
2022-06-27 21:01:46 +00:00
|
|
|
}}
|
2022-06-28 02:17:23 +00:00
|
|
|
onMonthChange={(newMonth) => {
|
|
|
|
setMonth(newMonth.format("YYYY-MM"));
|
2022-06-19 15:02:00 +00:00
|
|
|
}}
|
2022-06-27 21:01:46 +00:00
|
|
|
browsingDate={browsingDate}
|
2022-06-15 20:54:31 +00:00
|
|
|
weekStart={weekStart}
|
|
|
|
/>
|
|
|
|
|
2022-10-10 13:24:06 +00:00
|
|
|
<div ref={slotPickerRef}>
|
2022-06-15 20:54:31 +00:00
|
|
|
<AvailableTimes
|
2022-06-27 21:01:46 +00:00
|
|
|
isLoading={isLoading}
|
2022-10-10 13:24:06 +00:00
|
|
|
slots={selectedDate && slots[selectedDate.format("YYYY-MM-DD")]}
|
2022-06-27 21:01:46 +00:00
|
|
|
date={selectedDate}
|
2022-06-15 20:54:31 +00:00
|
|
|
timeFormat={timeFormat}
|
|
|
|
eventTypeId={eventType.id}
|
|
|
|
eventTypeSlug={eventType.slug}
|
|
|
|
seatsPerTimeSlot={seatsPerTimeSlot}
|
|
|
|
recurringCount={recurringEventCount}
|
2022-09-05 21:10:58 +00:00
|
|
|
ethSignature={ethSignature}
|
2022-06-15 20:54:31 +00:00
|
|
|
/>
|
2022-10-10 13:24:06 +00:00
|
|
|
</div>
|
2022-06-15 20:54:31 +00:00
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
function TimezoneDropdown({
|
|
|
|
onChangeTimeFormat,
|
|
|
|
onChangeTimeZone,
|
2022-06-27 21:01:46 +00:00
|
|
|
timeZone,
|
2022-07-26 18:02:44 +00:00
|
|
|
timeFormat,
|
2022-10-06 10:25:41 +00:00
|
|
|
hideTimeFormatToggle,
|
2022-06-15 20:54:31 +00:00
|
|
|
}: {
|
|
|
|
onChangeTimeFormat: (newTimeFormat: string) => void;
|
|
|
|
onChangeTimeZone: (newTimeZone: string) => void;
|
2022-06-27 21:01:46 +00:00
|
|
|
timeZone?: string;
|
2022-07-26 18:02:44 +00:00
|
|
|
timeFormat: string;
|
2022-10-06 10:25:41 +00:00
|
|
|
hideTimeFormatToggle?: boolean;
|
2022-06-15 20:54:31 +00:00
|
|
|
}) {
|
|
|
|
const [isTimeOptionsOpen, setIsTimeOptionsOpen] = useState(false);
|
|
|
|
|
|
|
|
useEffect(() => {
|
2022-09-30 12:45:28 +00:00
|
|
|
handleToggle24hClock(!!getIs24hClockFromLocalStorage());
|
2022-06-15 20:54:31 +00:00
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
const handleSelectTimeZone = (newTimeZone: string) => {
|
|
|
|
onChangeTimeZone(newTimeZone);
|
2022-06-27 21:01:46 +00:00
|
|
|
localStorageTimeZone(newTimeZone);
|
2022-06-15 20:54:31 +00:00
|
|
|
setIsTimeOptionsOpen(false);
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleToggle24hClock = (is24hClock: boolean) => {
|
|
|
|
onChangeTimeFormat(is24hClock ? "HH:mm" : "h:mma");
|
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
2022-10-05 13:11:26 +00:00
|
|
|
<Popover.Root open={isTimeOptionsOpen} onOpenChange={setIsTimeOptionsOpen}>
|
|
|
|
<Popover.Trigger className="min-w-32 dark:text-darkgray-600 radix-state-open:bg-gray-200 dark:radix-state-open:bg-darkgray-200 group relative mb-2 -ml-2 inline-block rounded-md px-2 py-2 text-left text-gray-600">
|
2022-08-29 16:01:45 +00:00
|
|
|
<p className="text-sm font-medium">
|
|
|
|
<Icon.FiGlobe className="mr-[10px] ml-[2px] -mt-[2px] inline-block h-4 w-4" />
|
2022-07-06 14:25:41 +00:00
|
|
|
{timeZone}
|
2022-07-01 11:28:17 +00:00
|
|
|
{isTimeOptionsOpen ? (
|
2022-09-14 17:32:52 +00:00
|
|
|
<Icon.FiChevronUp className="ml-1 inline-block h-4 w-4" />
|
2022-07-01 11:28:17 +00:00
|
|
|
) : (
|
2022-09-14 17:32:52 +00:00
|
|
|
<Icon.FiChevronDown className="ml-1 inline-block h-4 w-4" />
|
2022-07-01 11:28:17 +00:00
|
|
|
)}
|
|
|
|
</p>
|
2022-10-05 13:11:26 +00:00
|
|
|
</Popover.Trigger>
|
|
|
|
<Popover.Portal>
|
|
|
|
<Popover.Content
|
|
|
|
hideWhenDetached
|
|
|
|
align="start"
|
|
|
|
className="animate-fade-in-up absolute left-0 top-2 w-80 max-w-[calc(100vw_-_1.5rem)]">
|
|
|
|
<TimeOptions
|
|
|
|
onSelectTimeZone={handleSelectTimeZone}
|
|
|
|
onToggle24hClock={handleToggle24hClock}
|
|
|
|
timeFormat={timeFormat}
|
2022-10-06 10:25:41 +00:00
|
|
|
hideTimeFormatToggle={hideTimeFormatToggle}
|
2022-10-05 13:11:26 +00:00
|
|
|
/>
|
|
|
|
</Popover.Content>
|
|
|
|
</Popover.Portal>
|
|
|
|
</Popover.Root>
|
2022-06-15 20:54:31 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-06-27 21:01:46 +00:00
|
|
|
const dateQuerySchema = z.object({
|
|
|
|
rescheduleUid: z.string().optional().default(""),
|
|
|
|
date: z.string().optional().default(""),
|
|
|
|
timeZone: z.string().optional().default(""),
|
|
|
|
});
|
2022-06-15 20:54:31 +00:00
|
|
|
|
2022-06-28 02:17:23 +00:00
|
|
|
const useRouterQuery = <T extends string>(name: T) => {
|
|
|
|
const router = useRouter();
|
2022-08-05 17:08:47 +00:00
|
|
|
const existingQueryParams = router.asPath.split("?")[1];
|
|
|
|
|
|
|
|
const urlParams = new URLSearchParams(existingQueryParams);
|
|
|
|
const query = Object.fromEntries(urlParams);
|
2022-06-28 02:17:23 +00:00
|
|
|
|
|
|
|
const setQuery = (newValue: string | number | null | undefined) => {
|
|
|
|
router.replace({ query: { ...router.query, [name]: newValue } }, undefined, { shallow: true });
|
2022-08-05 17:08:47 +00:00
|
|
|
router.replace({ query: { ...router.query, ...query, [name]: newValue } }, undefined, { shallow: true });
|
2022-06-28 02:17:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
return { [name]: query[name], setQuery } as {
|
|
|
|
[K in T]: string | undefined;
|
|
|
|
} & { setQuery: typeof setQuery };
|
|
|
|
};
|
|
|
|
|
2022-09-05 21:10:58 +00:00
|
|
|
export type Props = AvailabilityTeamPageProps | AvailabilityPageProps | DynamicAvailabilityPageProps;
|
|
|
|
|
2022-10-06 10:25:41 +00:00
|
|
|
const timeFormatTotimeFormatString = (timeFormat?: number | null) => {
|
|
|
|
if (!timeFormat) return null;
|
|
|
|
return timeFormat === 24 ? "HH:mm" : "h:mma";
|
|
|
|
};
|
|
|
|
|
2022-06-15 20:54:31 +00:00
|
|
|
const AvailabilityPage = ({ profile, eventType }: Props) => {
|
2022-10-06 10:25:41 +00:00
|
|
|
const { data: user } = trpc.useQuery(["viewer.me"]);
|
|
|
|
const timeFormatFromProfile = timeFormatTotimeFormatString(user?.timeFormat);
|
2022-06-15 20:54:31 +00:00
|
|
|
const router = useRouter();
|
|
|
|
const isEmbed = useIsEmbed();
|
2022-06-27 21:01:46 +00:00
|
|
|
const query = dateQuerySchema.parse(router.query);
|
|
|
|
const { rescheduleUid } = query;
|
2022-07-26 08:27:57 +00:00
|
|
|
useTheme(profile.theme);
|
2022-06-16 02:07:07 +00:00
|
|
|
const { t } = useLocale();
|
2022-06-15 20:54:31 +00:00
|
|
|
const availabilityDatePickerEmbedStyles = useEmbedStyles("availabilityDatePicker");
|
|
|
|
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
|
|
|
|
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
|
|
|
|
const isBackgroundTransparent = useIsBackgroundTransparent();
|
|
|
|
|
|
|
|
const [timeZone, setTimeZone] = useState<string>();
|
2022-10-14 19:15:03 +00:00
|
|
|
const [timeFormat, setTimeFormat] = useState<string>("HH:mm");
|
|
|
|
|
2022-09-05 21:10:58 +00:00
|
|
|
const [gateState, gateDispatcher] = useReducer(
|
|
|
|
(state: GateState, newState: Partial<GateState>) => ({
|
|
|
|
...state,
|
|
|
|
...newState,
|
|
|
|
}),
|
|
|
|
{}
|
|
|
|
);
|
2022-02-23 12:37:15 +00:00
|
|
|
|
2022-06-15 20:54:31 +00:00
|
|
|
useEffect(() => {
|
2022-06-27 21:01:46 +00:00
|
|
|
setTimeZone(localStorageTimeZone() || dayjs.tz.guess());
|
2022-10-14 19:15:03 +00:00
|
|
|
setTimeFormat(timeFormatFromProfile || detectBrowserTimeFormat);
|
|
|
|
}, [timeFormatFromProfile]);
|
2022-06-27 21:01:46 +00:00
|
|
|
|
2022-06-15 20:54:31 +00:00
|
|
|
// TODO: Improve this;
|
|
|
|
useExposePlanGlobally(eventType.users.length === 1 ? eventType.users[0].plan : "PRO");
|
2021-09-14 08:45:28 +00:00
|
|
|
|
2022-06-15 20:54:31 +00:00
|
|
|
const [recurringEventCount, setRecurringEventCount] = useState(eventType.recurringEvent?.count);
|
2022-02-23 12:37:15 +00:00
|
|
|
|
2022-06-15 20:54:31 +00:00
|
|
|
const telemetry = useTelemetry();
|
|
|
|
useEffect(() => {
|
2022-06-02 16:19:01 +00:00
|
|
|
if (top !== window) {
|
|
|
|
//page_view will be collected automatically by _middleware.ts
|
|
|
|
telemetry.event(
|
|
|
|
telemetryEventTypes.embedView,
|
2022-05-11 05:14:08 +00:00
|
|
|
collectPageParameters("/availability", { isTeamBooking: document.URL.includes("team/") })
|
2022-06-02 16:19:01 +00:00
|
|
|
);
|
|
|
|
}
|
2021-09-14 08:45:28 +00:00
|
|
|
}, [telemetry]);
|
|
|
|
|
2022-08-12 19:29:29 +00:00
|
|
|
// get dynamic user list here
|
2022-09-05 21:10:58 +00:00
|
|
|
const userList = eventType.users ? eventType.users.map((user) => user.username).filter(notEmpty) : [];
|
2022-10-10 13:24:06 +00:00
|
|
|
|
2022-06-27 21:01:46 +00:00
|
|
|
const timezoneDropdown = useMemo(
|
|
|
|
() => (
|
|
|
|
<TimezoneDropdown
|
2022-07-26 18:02:44 +00:00
|
|
|
timeFormat={timeFormat}
|
2022-06-27 21:01:46 +00:00
|
|
|
onChangeTimeFormat={setTimeFormat}
|
|
|
|
timeZone={timeZone}
|
|
|
|
onChangeTimeZone={setTimeZone}
|
2022-10-06 10:25:41 +00:00
|
|
|
// Currently we don't allow the user to change the timeformat when they're logged in,
|
|
|
|
// the only way to change it is if they go to their profile.
|
|
|
|
hideTimeFormatToggle={!!timeFormatFromProfile}
|
2022-06-27 21:01:46 +00:00
|
|
|
/>
|
|
|
|
),
|
2022-10-06 10:25:41 +00:00
|
|
|
[timeZone, timeFormat, timeFormatFromProfile]
|
2022-06-15 20:54:31 +00:00
|
|
|
);
|
2022-10-14 16:24:43 +00:00
|
|
|
const stripeAppData = getStripeAppData(eventType);
|
|
|
|
const rainbowAppData = getEventTypeAppData(eventType, "rainbow") || {};
|
2022-07-15 16:54:14 +00:00
|
|
|
const rawSlug = profile.slug ? profile.slug.split("/") : [];
|
|
|
|
if (rawSlug.length > 1) rawSlug.pop(); //team events have team name as slug, but user events have [user]/[type] as slug.
|
2022-06-15 20:54:31 +00:00
|
|
|
|
2022-09-05 21:10:58 +00:00
|
|
|
// Define conditional gates here
|
|
|
|
const gates = [
|
|
|
|
// Rainbow gate is only added if the event has both a `blockchainId` and a `smartContractAddress`
|
2022-10-14 16:24:43 +00:00
|
|
|
rainbowAppData && rainbowAppData.blockchainId && rainbowAppData.smartContractAddress
|
2022-09-05 21:10:58 +00:00
|
|
|
? ("rainbow" as Gate)
|
|
|
|
: undefined,
|
|
|
|
];
|
|
|
|
|
2021-09-14 08:45:28 +00:00
|
|
|
return (
|
2022-10-14 16:24:43 +00:00
|
|
|
<Gates gates={gates} appData={rainbowAppData} dispatch={gateDispatcher}>
|
2021-09-24 22:11:30 +00:00
|
|
|
<HeadSeo
|
2021-10-08 11:43:48 +00:00
|
|
|
title={`${rescheduleUid ? t("reschedule") : ""} ${eventType.title} | ${profile.name}`}
|
|
|
|
description={`${rescheduleUid ? t("reschedule") : ""} ${eventType.title}`}
|
2022-10-18 17:46:22 +00:00
|
|
|
meeting={{
|
|
|
|
title: eventType.title,
|
|
|
|
profile: { name: `${profile.name}`, image: profile.image },
|
|
|
|
users: [
|
|
|
|
...(eventType.users || []).map((user) => ({
|
|
|
|
name: `${user.name}`,
|
|
|
|
username: `${user.username}`,
|
|
|
|
})),
|
|
|
|
],
|
|
|
|
}}
|
2022-07-26 00:17:15 +00:00
|
|
|
nextSeoProps={{
|
|
|
|
nofollow: eventType.hidden,
|
|
|
|
noindex: eventType.hidden,
|
|
|
|
}}
|
2021-09-24 22:11:30 +00:00
|
|
|
/>
|
2022-10-14 16:24:43 +00:00
|
|
|
<BookingPageTagManager eventType={eventType} />
|
2022-03-05 15:37:46 +00:00
|
|
|
<CustomBranding lightVal={profile.brandColor} darkVal={profile.darkBrandColor} />
|
2021-09-24 22:11:30 +00:00
|
|
|
<div>
|
|
|
|
<main
|
2022-04-25 04:33:00 +00:00
|
|
|
className={classNames(
|
2022-10-14 18:04:54 +00:00
|
|
|
"flex-col md:mx-4 lg:flex",
|
2022-10-10 13:24:06 +00:00
|
|
|
shouldAlignCentrally ? "items-center" : "items-start",
|
|
|
|
!isEmbed && classNames("mx-auto my-0 ease-in-out md:my-24")
|
2022-04-25 04:33:00 +00:00
|
|
|
)}>
|
2022-10-10 13:24:06 +00:00
|
|
|
<div>
|
|
|
|
<div
|
|
|
|
style={availabilityDatePickerEmbedStyles}
|
|
|
|
className={classNames(
|
|
|
|
isBackgroundTransparent
|
|
|
|
? ""
|
|
|
|
: "dark:bg-darkgray-100 sm:dark:border-darkgray-300 bg-white pb-4 md:pb-0",
|
2022-10-14 18:04:54 +00:00
|
|
|
"border-bookinglightest overflow-hidden md:rounded-md md:border",
|
2022-10-10 13:24:06 +00:00
|
|
|
isEmbed && "mx-auto"
|
|
|
|
)}>
|
|
|
|
<div className="overflow-hidden md:flex">
|
|
|
|
<div
|
|
|
|
className={classNames(
|
|
|
|
"sm:dark:border-darkgray-200 flex flex-col border-gray-200 p-5 sm:border-r",
|
|
|
|
"min-w-full md:w-[280px] md:min-w-[280px]",
|
|
|
|
recurringEventCount && "xl:w-[380px] xl:min-w-[380px]"
|
|
|
|
)}>
|
2022-10-17 13:47:11 +00:00
|
|
|
<BookingDescription profile={profile} eventType={eventType} rescheduleUid={rescheduleUid}>
|
2022-10-10 13:24:06 +00:00
|
|
|
{!rescheduleUid && eventType.recurringEvent && (
|
|
|
|
<div className="flex items-start text-sm font-medium">
|
|
|
|
<Icon.FiRefreshCcw className="float-left mr-[10px] mt-[7px] ml-[2px] inline-block h-4 w-4 " />
|
|
|
|
<div>
|
|
|
|
<p className="mb-1 -ml-2 inline px-2 py-1">
|
|
|
|
{getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })}
|
|
|
|
</p>
|
|
|
|
<input
|
|
|
|
type="number"
|
|
|
|
min="1"
|
|
|
|
max={eventType.recurringEvent.count}
|
|
|
|
className="w-15 dark:bg-darkgray-200 h-7 rounded-sm border-gray-300 bg-white text-sm font-medium [appearance:textfield] ltr:mr-2 rtl:ml-2 dark:border-gray-500"
|
|
|
|
defaultValue={eventType.recurringEvent.count}
|
|
|
|
onChange={(event) => {
|
|
|
|
setRecurringEventCount(parseInt(event?.target.value));
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
<p className="inline">
|
|
|
|
{t("occurrence", {
|
|
|
|
count: recurringEventCount,
|
|
|
|
})}
|
|
|
|
</p>
|
2022-06-16 02:07:07 +00:00
|
|
|
</div>
|
2022-04-17 11:25:11 +00:00
|
|
|
</div>
|
2022-10-10 13:24:06 +00:00
|
|
|
)}
|
2022-10-14 16:24:43 +00:00
|
|
|
{stripeAppData.price > 0 && (
|
2022-10-10 13:24:06 +00:00
|
|
|
<p className="-ml-2 px-2 text-sm font-medium">
|
|
|
|
<Icon.FiCreditCard className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
|
|
|
|
<IntlProvider locale="en">
|
|
|
|
<FormattedNumber
|
2022-10-14 16:24:43 +00:00
|
|
|
value={stripeAppData.price / 100.0}
|
2022-10-10 13:24:06 +00:00
|
|
|
style="currency"
|
2022-10-14 16:24:43 +00:00
|
|
|
currency={stripeAppData.currency.toUpperCase()}
|
2022-10-10 13:24:06 +00:00
|
|
|
/>
|
|
|
|
</IntlProvider>
|
|
|
|
</p>
|
|
|
|
)}
|
|
|
|
{timezoneDropdown}
|
2022-10-12 08:39:14 +00:00
|
|
|
</BookingDescription>
|
2021-09-14 08:45:28 +00:00
|
|
|
|
2022-10-10 13:24:06 +00:00
|
|
|
{!isEmbed && (
|
|
|
|
<div className="mt-auto hidden md:block">
|
|
|
|
<GoBackToPreviousPage t={t} />
|
2022-06-16 02:07:07 +00:00
|
|
|
</div>
|
|
|
|
)}
|
2022-06-01 12:58:46 +00:00
|
|
|
|
2022-10-10 13:24:06 +00:00
|
|
|
{/* Temporarily disabled - booking?.startTime && rescheduleUid && (
|
2022-04-14 21:25:24 +00:00
|
|
|
<div>
|
2022-04-18 10:25:56 +00:00
|
|
|
<p
|
2022-08-24 20:18:42 +00:00
|
|
|
className="mt-4 mb-3 text-gray-600 dark:text-darkgray-600"
|
2022-04-18 10:25:56 +00:00
|
|
|
data-testid="former_time_p_desktop">
|
2022-04-14 21:25:24 +00:00
|
|
|
{t("former_time")}
|
|
|
|
</p>
|
2022-08-24 20:18:42 +00:00
|
|
|
<p className="text-gray-500 line-through dark:text-darkgray-600">
|
2022-08-05 08:46:44 +00:00
|
|
|
<CalendarIcon className="mr-[10px] -mt-1 inline-block h-4 w-4 text-gray-500" />
|
2022-04-14 21:25:24 +00:00
|
|
|
{typeof booking.startTime === "string" && parseDate(dayjs(booking.startTime), i18n)}
|
|
|
|
</p>
|
|
|
|
</div>
|
2022-06-15 20:54:31 +00:00
|
|
|
)*/}
|
2022-10-10 13:24:06 +00:00
|
|
|
</div>
|
|
|
|
<SlotPicker
|
|
|
|
weekStart={
|
|
|
|
typeof profile.weekStart === "string"
|
|
|
|
? ([
|
|
|
|
"Sunday",
|
|
|
|
"Monday",
|
|
|
|
"Tuesday",
|
|
|
|
"Wednesday",
|
|
|
|
"Thursday",
|
|
|
|
"Friday",
|
|
|
|
"Saturday",
|
|
|
|
].indexOf(profile.weekStart) as 0 | 1 | 2 | 3 | 4 | 5 | 6)
|
|
|
|
: profile.weekStart /* Allows providing weekStart as number */
|
|
|
|
}
|
|
|
|
eventType={eventType}
|
|
|
|
timeFormat={timeFormat}
|
|
|
|
timeZone={timeZone}
|
|
|
|
users={userList}
|
|
|
|
seatsPerTimeSlot={eventType.seatsPerTimeSlot || undefined}
|
|
|
|
recurringEventCount={recurringEventCount}
|
|
|
|
ethSignature={gateState.rainbowToken}
|
|
|
|
/>
|
2021-09-14 08:45:28 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
2022-10-10 13:24:06 +00:00
|
|
|
{(!eventType.users[0] || !isBrandingHidden(eventType.users[0])) && !isEmbed && <PoweredByCal />}
|
2022-06-16 02:07:07 +00:00
|
|
|
</div>
|
2021-09-24 22:11:30 +00:00
|
|
|
</main>
|
|
|
|
</div>
|
2022-09-05 21:10:58 +00:00
|
|
|
<Toaster position="bottom-right" />
|
|
|
|
</Gates>
|
2021-09-14 08:45:28 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default AvailabilityPage;
|