// TODO: replace headlessui with radix-ui import { Menu, Transition } from "@headlessui/react"; import { DotsHorizontalIcon, ExternalLinkIcon, LinkIcon, ArrowDownIcon, ChevronDownIcon, PlusIcon, ArrowUpIcon, UsersIcon, } from "@heroicons/react/solid"; import { SchedulingType } from "@prisma/client"; import Head from "next/head"; import Link from "next/link"; import { useRouter } from "next/router"; import React, { Fragment, useRef, useState, useEffect } from "react"; import { useMutation } from "react-query"; import { QueryCell } from "@lib/QueryCell"; import classNames from "@lib/classNames"; import { HttpError } from "@lib/core/http/error"; import { useLocale } from "@lib/hooks/useLocale"; import { useToggleQuery } from "@lib/hooks/useToggleQuery"; import createEventType from "@lib/mutations/event-types/create-event-type"; import showToast from "@lib/notification"; import { inferQueryOutput, trpc } from "@lib/trpc"; import { CreateEventType } from "@lib/types/event-type"; import { Dialog, DialogClose, DialogContent } from "@components/Dialog"; import Shell from "@components/Shell"; import { Tooltip } from "@components/Tooltip"; import EventTypeDescription from "@components/eventtype/EventTypeDescription"; import { Alert } from "@components/ui/Alert"; import Avatar from "@components/ui/Avatar"; import AvatarGroup from "@components/ui/AvatarGroup"; import Badge from "@components/ui/Badge"; import { Button } from "@components/ui/Button"; import Dropdown, { DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@components/ui/Dropdown"; import * as RadioArea from "@components/ui/form/radio-area"; import UserCalendarIllustration from "@components/ui/svg/UserCalendarIllustration"; type Profiles = inferQueryOutput<"viewer.eventTypes">["profiles"]; type EventTypeGroups = inferQueryOutput<"viewer.eventTypes">["eventTypeGroups"]; type EventTypeGroupProfile = EventTypeGroups[number]["profile"]; interface CreateEventTypeProps { canAddEvents: boolean; profiles: Profiles; } const CreateFirstEventTypeView = ({ canAddEvents, profiles }: CreateEventTypeProps) => { const { t } = useLocale(); return (

{t("new_event_type_heading")}

{t("new_event_type_description")}

); }; type EventTypeGroup = inferQueryOutput<"viewer.eventTypes">["eventTypeGroups"][number]; type EventType = EventTypeGroup["eventTypes"][number]; interface EventTypeListProps { profile: { slug: string | null }; readOnly: boolean; types: EventType[]; } const EventTypeList = ({ readOnly, types, profile }: EventTypeListProps): JSX.Element => { const { t } = useLocale(); const utils = trpc.useContext(); const mutation = trpc.useMutation("viewer.eventTypeOrder", { onError: (err) => { console.error(err.message); }, async onSettled() { await utils.cancelQuery(["viewer.eventTypes"]); await utils.invalidateQueries(["viewer.eventTypes"]); }, }); const [sortableTypes, setSortableTypes] = useState(types); useEffect(() => { setSortableTypes(types); }, [types]); function moveEventType(index: number, increment: 1 | -1) { const newList = [...sortableTypes]; const type = sortableTypes[index]; const tmp = sortableTypes[index + increment]; if (tmp) { newList[index] = tmp; newList[index + increment] = type; } setSortableTypes(newList); mutation.mutate({ ids: newList.map((type) => type.id), }); } return (
); }; interface EventTypeListHeadingProps { profile: EventTypeGroupProfile; membershipCount: number; } const EventTypeListHeading = ({ profile, membershipCount }: EventTypeListHeadingProps): JSX.Element => (
{profile?.name || ""} {membershipCount && ( {membershipCount} )} {profile?.slug && ( {`${process.env.NEXT_PUBLIC_APP_URL?.replace( "https://", "" )}/${profile.slug}`} )}
); const EventTypesPage = () => { const { t } = useLocale(); const query = trpc.useQuery(["viewer.eventTypes"]); return (
Home | Cal.com ) }> ( <> {data.viewer.plan === "FREE" && !data.viewer.canAddEvents && ( {t("plan_upgrade")}} message={ <> {t("to_upgrade_go_to")}{" "} {"https://cal.com/upgrade"} } className="mb-4" /> )} {data.eventTypeGroups.map((group) => ( {/* hide list heading when there is only one (current user) */} {(data.eventTypeGroups.length !== 1 || group.teamId) && ( )} ))} {data.eventTypeGroups.length === 0 && ( )} )} />
); }; const CreateNewEventButton = ({ profiles, canAddEvents }: CreateEventTypeProps) => { const router = useRouter(); const teamId: number | null = Number(router.query.teamId) || null; const modalOpen = useToggleQuery("new"); const { t } = useLocale(); const createMutation = useMutation(createEventType, { onSuccess: async ({ eventType }) => { await router.push("/event-types/" + eventType.id); showToast(t("event_type_created_successfully", { eventTypeTitle: eventType.title }), "success"); }, onError: (err: HttpError) => { const message = `${err.statusCode}: ${err.message}`; showToast(message, "error"); }, }); const slugRef = useRef(null); return ( { router.push(isOpen ? modalOpen.hrefOn : modalOpen.hrefOff); }}> {!profiles.filter((profile) => profile.teamId).length && ( )} {profiles.filter((profile) => profile.teamId).length > 0 && ( {t("new_event_subtitle")} {profiles.map((profile) => ( router.push({ pathname: router.pathname, query: { ...router.query, new: "1", eventPage: profile.slug, ...(profile.teamId ? { teamId: profile.teamId, } : {}), }, }) }> {profile.name ? profile.name : profile.slug} ))} )}

{t("new_event_type_to_book_description")}

{ e.preventDefault(); const target = e.target as unknown as Record< "title" | "slug" | "description" | "length" | "schedulingType", { value: string } >; const payload: CreateEventType = { title: target.title.value, slug: target.slug.value, description: target.description.value, length: parseInt(target.length.value), }; if (router.query.teamId) { payload.teamId = parseInt(`${router.query.teamId}`, 10); payload.schedulingType = target.schedulingType.value as SchedulingType; } createMutation.mutate(payload); }}>
{ if (!slugRef.current) { return; } slugRef.current.value = e.target.value.replace(/\s+/g, "-").toLowerCase(); }} type="text" name="title" id="title" required className="block w-full border-gray-300 rounded-sm shadow-sm focus:ring-neutral-900 focus:border-neutral-900 sm:text-sm" placeholder={t("quick_chat")} />
{process.env.NEXT_PUBLIC_APP_URL}/{router.query.eventPage || profiles[0].slug}/