* Back Button Events - [2058] * Fixing URL Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
		
			
				
	
	
		
			294 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			294 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
// Get router variables
 | 
						|
import {
 | 
						|
  ArrowLeftIcon,
 | 
						|
  ChevronDownIcon,
 | 
						|
  ChevronUpIcon,
 | 
						|
  ClockIcon,
 | 
						|
  CreditCardIcon,
 | 
						|
  GlobeIcon,
 | 
						|
} from "@heroicons/react/solid";
 | 
						|
import * as Collapsible from "@radix-ui/react-collapsible";
 | 
						|
import { useContracts } from "contexts/contractsContext";
 | 
						|
import dayjs, { Dayjs } from "dayjs";
 | 
						|
import customParseFormat from "dayjs/plugin/customParseFormat";
 | 
						|
import utc from "dayjs/plugin/utc";
 | 
						|
import { useRouter } from "next/router";
 | 
						|
import { useEffect, useMemo, useState } from "react";
 | 
						|
import { FormattedNumber, IntlProvider } from "react-intl";
 | 
						|
 | 
						|
import { asStringOrNull } from "@lib/asStringOrNull";
 | 
						|
import { timeZone } from "@lib/clock";
 | 
						|
import { BASE_URL } from "@lib/config/constants";
 | 
						|
import { useLocale } from "@lib/hooks/useLocale";
 | 
						|
import useTheme from "@lib/hooks/useTheme";
 | 
						|
import { isBrandingHidden } from "@lib/isBrandingHidden";
 | 
						|
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
 | 
						|
import { detectBrowserTimeFormat } from "@lib/timeFormat";
 | 
						|
 | 
						|
import CustomBranding from "@components/CustomBranding";
 | 
						|
import AvailableTimes from "@components/booking/AvailableTimes";
 | 
						|
import DatePicker from "@components/booking/DatePicker";
 | 
						|
import TimeOptions from "@components/booking/TimeOptions";
 | 
						|
import { HeadSeo } from "@components/seo/head-seo";
 | 
						|
import AvatarGroup from "@components/ui/AvatarGroup";
 | 
						|
import PoweredByCal from "@components/ui/PoweredByCal";
 | 
						|
 | 
						|
import { AvailabilityPageProps } from "../../../pages/[user]/[type]";
 | 
						|
import { AvailabilityTeamPageProps } from "../../../pages/team/[slug]/[type]";
 | 
						|
 | 
						|
dayjs.extend(utc);
 | 
						|
dayjs.extend(customParseFormat);
 | 
						|
 | 
						|
type Props = AvailabilityTeamPageProps | AvailabilityPageProps;
 | 
						|
 | 
						|
const AvailabilityPage = ({ profile, eventType, workingHours, previousPage }: Props) => {
 | 
						|
  const router = useRouter();
 | 
						|
  const { rescheduleUid } = router.query;
 | 
						|
  const { isReady, Theme } = useTheme(profile.theme);
 | 
						|
  const { t } = useLocale();
 | 
						|
  const { contracts } = useContracts();
 | 
						|
 | 
						|
  useEffect(() => {
 | 
						|
    if (eventType.metadata.smartContractAddress) {
 | 
						|
      const eventOwner = eventType.users[0];
 | 
						|
      if (!contracts[(eventType.metadata.smartContractAddress || null) as number])
 | 
						|
        router.replace(`/${eventOwner.username}`);
 | 
						|
    }
 | 
						|
  }, [contracts, eventType.metadata.smartContractAddress, router]);
 | 
						|
 | 
						|
  const selectedDate = useMemo(() => {
 | 
						|
    const dateString = asStringOrNull(router.query.date);
 | 
						|
    if (dateString) {
 | 
						|
      // todo some extra validation maybe.
 | 
						|
      const utcOffsetAsDate = dayjs(dateString.substr(11, 14), "Hmm");
 | 
						|
      const utcOffset = parseInt(
 | 
						|
        dateString.substr(10, 1) + (utcOffsetAsDate.hour() * 60 + utcOffsetAsDate.minute())
 | 
						|
      );
 | 
						|
      const date = dayjs(dateString.substr(0, 10)).utcOffset(utcOffset, true);
 | 
						|
      return date.isValid() ? date : null;
 | 
						|
    }
 | 
						|
    return null;
 | 
						|
  }, [router.query.date]);
 | 
						|
 | 
						|
  const [isTimeOptionsOpen, setIsTimeOptionsOpen] = useState(false);
 | 
						|
  const [timeFormat, setTimeFormat] = useState(detectBrowserTimeFormat);
 | 
						|
 | 
						|
  const telemetry = useTelemetry();
 | 
						|
 | 
						|
  useEffect(() => {
 | 
						|
    handleToggle24hClock(localStorage.getItem("timeOption.is24hClock") === "true");
 | 
						|
 | 
						|
    telemetry.withJitsu((jitsu) => jitsu.track(telemetryEventTypes.pageView, collectPageParameters()));
 | 
						|
  }, [telemetry]);
 | 
						|
 | 
						|
  const changeDate = (newDate: Dayjs) => {
 | 
						|
    router.replace(
 | 
						|
      {
 | 
						|
        query: {
 | 
						|
          ...router.query,
 | 
						|
          date: newDate.format("YYYY-MM-DDZZ"),
 | 
						|
        },
 | 
						|
      },
 | 
						|
      undefined,
 | 
						|
      {
 | 
						|
        shallow: true,
 | 
						|
      }
 | 
						|
    );
 | 
						|
  };
 | 
						|
 | 
						|
  const handleSelectTimeZone = (selectedTimeZone: string): void => {
 | 
						|
    if (selectedDate) {
 | 
						|
      changeDate(selectedDate.tz(selectedTimeZone, true));
 | 
						|
    }
 | 
						|
    timeZone(selectedTimeZone);
 | 
						|
    setIsTimeOptionsOpen(false);
 | 
						|
  };
 | 
						|
 | 
						|
  const handleToggle24hClock = (is24hClock: boolean) => {
 | 
						|
    setTimeFormat(is24hClock ? "HH:mm" : "h:mma");
 | 
						|
  };
 | 
						|
 | 
						|
  return (
 | 
						|
    <>
 | 
						|
      <Theme />
 | 
						|
      <HeadSeo
 | 
						|
        title={`${rescheduleUid ? t("reschedule") : ""} ${eventType.title} | ${profile.name}`}
 | 
						|
        description={`${rescheduleUid ? t("reschedule") : ""} ${eventType.title}`}
 | 
						|
        name={profile.name || undefined}
 | 
						|
        username={profile.slug || undefined}
 | 
						|
        // avatar={profile.image || undefined}
 | 
						|
      />
 | 
						|
      <CustomBranding lightVal={profile.brandColor} darkVal={profile.darkBrandColor} />
 | 
						|
      <div>
 | 
						|
        <main
 | 
						|
          className={
 | 
						|
            "transition-max-width mx-auto my-0 duration-500 ease-in-out md:my-24 " +
 | 
						|
            (selectedDate ? "max-w-5xl" : "max-w-3xl")
 | 
						|
          }>
 | 
						|
          {isReady && (
 | 
						|
            <div className="rounded-sm border-gray-200 bg-white dark:bg-gray-900 sm:dark:border-gray-600 md:border">
 | 
						|
              {/* mobile: details */}
 | 
						|
              <div className="block p-4 sm:p-8 md:hidden">
 | 
						|
                <div className="flex items-center">
 | 
						|
                  <AvatarGroup
 | 
						|
                    border="border-2 dark:border-gray-900 border-white"
 | 
						|
                    items={
 | 
						|
                      [
 | 
						|
                        { image: profile.image, alt: profile.name, title: profile.name },
 | 
						|
                        ...eventType.users
 | 
						|
                          .filter((user) => user.name !== profile.name)
 | 
						|
                          .map((user) => ({
 | 
						|
                            title: user.name,
 | 
						|
                            image: `${process.env.NEXT_PUBLIC_APP_URL}/${user.username}/avatar.png`,
 | 
						|
                            alt: user.name || undefined,
 | 
						|
                          })),
 | 
						|
                      ].filter((item) => !!item.image) as { image: string; alt?: string; title?: string }[]
 | 
						|
                    }
 | 
						|
                    size={9}
 | 
						|
                    truncateAfter={5}
 | 
						|
                  />
 | 
						|
                  <div className="ltr:ml-3 rtl:mr-3">
 | 
						|
                    <p className="text-sm font-medium text-black dark:text-gray-300">{profile.name}</p>
 | 
						|
                    <div className="flex gap-2 text-xs font-medium text-gray-600">
 | 
						|
                      {eventType.title}
 | 
						|
                      <div>
 | 
						|
                        <ClockIcon className="mr-1 -mt-1 inline-block h-4 w-4" />
 | 
						|
                        {eventType.length} {t("minutes")}
 | 
						|
                      </div>
 | 
						|
                      {eventType.price > 0 && (
 | 
						|
                        <div>
 | 
						|
                          <CreditCardIcon className="mr-1 -mt-1 inline-block h-4 w-4" />
 | 
						|
                          <IntlProvider locale="en">
 | 
						|
                            <FormattedNumber
 | 
						|
                              value={eventType.price / 100.0}
 | 
						|
                              style="currency"
 | 
						|
                              currency={eventType.currency.toUpperCase()}
 | 
						|
                            />
 | 
						|
                          </IntlProvider>
 | 
						|
                        </div>
 | 
						|
                      )}
 | 
						|
                    </div>
 | 
						|
                  </div>
 | 
						|
                </div>
 | 
						|
                <p className="mt-3 text-gray-600 dark:text-gray-200">{eventType.description}</p>
 | 
						|
              </div>
 | 
						|
 | 
						|
              <div className="px-4 sm:flex sm:p-4 sm:py-5">
 | 
						|
                <div
 | 
						|
                  className={
 | 
						|
                    "hidden pr-8 sm:border-r sm:dark:border-gray-800 md:flex md:flex-col " +
 | 
						|
                    (selectedDate ? "sm:w-1/3" : "sm:w-1/2")
 | 
						|
                  }>
 | 
						|
                  <AvatarGroup
 | 
						|
                    border="border-2 dark:border-gray-900 border-white"
 | 
						|
                    items={
 | 
						|
                      [
 | 
						|
                        { image: profile.image, alt: profile.name, title: profile.name },
 | 
						|
                        ...eventType.users
 | 
						|
                          .filter((user) => user.name !== profile.name)
 | 
						|
                          .map((user) => ({
 | 
						|
                            title: user.name,
 | 
						|
                            alt: user.name,
 | 
						|
                            image: `${process.env.NEXT_PUBLIC_APP_URL}/${user.username}/avatar.png`,
 | 
						|
                          })),
 | 
						|
                      ].filter((item) => !!item.image) as { image: string; alt?: string; title?: string }[]
 | 
						|
                    }
 | 
						|
                    size={10}
 | 
						|
                    truncateAfter={3}
 | 
						|
                  />
 | 
						|
                  <h2 className="mt-3 font-medium text-gray-500 dark:text-gray-300">{profile.name}</h2>
 | 
						|
                  <h1 className="font-cal mb-4 text-3xl font-semibold text-gray-800 dark:text-white">
 | 
						|
                    {eventType.title}
 | 
						|
                  </h1>
 | 
						|
                  <p className="mb-1 -ml-2 px-2 py-1 text-gray-500">
 | 
						|
                    <ClockIcon className="mr-1 -mt-1 inline-block h-4 w-4" />
 | 
						|
                    {eventType.length} {t("minutes")}
 | 
						|
                  </p>
 | 
						|
                  {eventType.price > 0 && (
 | 
						|
                    <p className="mb-1 -ml-2 px-2 py-1 text-gray-500">
 | 
						|
                      <CreditCardIcon className="mr-1 -mt-1 inline-block h-4 w-4" />
 | 
						|
                      <IntlProvider locale="en">
 | 
						|
                        <FormattedNumber
 | 
						|
                          value={eventType.price / 100.0}
 | 
						|
                          style="currency"
 | 
						|
                          currency={eventType.currency.toUpperCase()}
 | 
						|
                        />
 | 
						|
                      </IntlProvider>
 | 
						|
                    </p>
 | 
						|
                  )}
 | 
						|
 | 
						|
                  <TimezoneDropdown />
 | 
						|
 | 
						|
                  <p className="mt-3 mb-8 text-gray-600 dark:text-gray-200">{eventType.description}</p>
 | 
						|
                  {previousPage === `${BASE_URL}/${profile.slug}` && (
 | 
						|
                    <div className="flex h-full flex-col justify-end">
 | 
						|
                      <ArrowLeftIcon
 | 
						|
                        className="h-4 w-4 text-black  transition-opacity hover:cursor-pointer dark:text-white"
 | 
						|
                        onClick={() => router.back()}
 | 
						|
                      />
 | 
						|
                      <p className="sr-only">Go Back</p>
 | 
						|
                    </div>
 | 
						|
                  )}
 | 
						|
                </div>
 | 
						|
                <DatePicker
 | 
						|
                  date={selectedDate}
 | 
						|
                  periodType={eventType?.periodType}
 | 
						|
                  periodStartDate={eventType?.periodStartDate}
 | 
						|
                  periodEndDate={eventType?.periodEndDate}
 | 
						|
                  periodDays={eventType?.periodDays}
 | 
						|
                  periodCountCalendarDays={eventType?.periodCountCalendarDays}
 | 
						|
                  onDatePicked={changeDate}
 | 
						|
                  workingHours={workingHours}
 | 
						|
                  weekStart={profile.weekStart || "Sunday"}
 | 
						|
                  eventLength={eventType.length}
 | 
						|
                  minimumBookingNotice={eventType.minimumBookingNotice}
 | 
						|
                />
 | 
						|
 | 
						|
                <div className="mt-4 ml-1 block sm:hidden">
 | 
						|
                  <TimezoneDropdown />
 | 
						|
                </div>
 | 
						|
 | 
						|
                {selectedDate && (
 | 
						|
                  <AvailableTimes
 | 
						|
                    timeFormat={timeFormat}
 | 
						|
                    minimumBookingNotice={eventType.minimumBookingNotice}
 | 
						|
                    eventTypeId={eventType.id}
 | 
						|
                    slotInterval={eventType.slotInterval}
 | 
						|
                    eventLength={eventType.length}
 | 
						|
                    date={selectedDate}
 | 
						|
                    users={eventType.users}
 | 
						|
                    schedulingType={eventType.schedulingType ?? null}
 | 
						|
                    beforeBufferTime={eventType.beforeEventBuffer}
 | 
						|
                    afterBufferTime={eventType.afterEventBuffer}
 | 
						|
                  />
 | 
						|
                )}
 | 
						|
              </div>
 | 
						|
            </div>
 | 
						|
          )}
 | 
						|
          {(!eventType.users[0] || !isBrandingHidden(eventType.users[0])) && <PoweredByCal />}
 | 
						|
        </main>
 | 
						|
      </div>
 | 
						|
    </>
 | 
						|
  );
 | 
						|
 | 
						|
  function TimezoneDropdown() {
 | 
						|
    return (
 | 
						|
      <Collapsible.Root open={isTimeOptionsOpen} onOpenChange={setIsTimeOptionsOpen}>
 | 
						|
        <Collapsible.Trigger className="min-w-32 mb-1 -ml-2 px-2 py-1 text-left text-gray-500">
 | 
						|
          <GlobeIcon className="mr-1 -mt-1 inline-block h-4 w-4" />
 | 
						|
          {timeZone()}
 | 
						|
          {isTimeOptionsOpen ? (
 | 
						|
            <ChevronUpIcon className="ml-1 -mt-1 inline-block h-4 w-4" />
 | 
						|
          ) : (
 | 
						|
            <ChevronDownIcon className="ml-1 -mt-1 inline-block h-4 w-4" />
 | 
						|
          )}
 | 
						|
        </Collapsible.Trigger>
 | 
						|
        <Collapsible.Content>
 | 
						|
          <TimeOptions onSelectTimeZone={handleSelectTimeZone} onToggle24hClock={handleToggle24hClock} />
 | 
						|
        </Collapsible.Content>
 | 
						|
      </Collapsible.Root>
 | 
						|
    );
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
export default AvailabilityPage;
 |