import { ChevronDownIcon, PlusIcon } from "@heroicons/react/solid"; import { zodResolver } from "@hookform/resolvers/zod/dist/zod"; import { SchedulingType } from "@prisma/client"; import { useRouter } from "next/router"; import { createEventTypeInput } from "prisma/zod/eventtypeCustom"; import React, { useEffect } from "react"; import { useForm } from "react-hook-form"; import type { z } from "zod"; import { HttpError } from "@lib/core/http/error"; import { useLocale } from "@lib/hooks/useLocale"; import { useToggleQuery } from "@lib/hooks/useToggleQuery"; import showToast from "@lib/notification"; import { trpc } from "@lib/trpc"; import { Dialog, DialogClose, DialogContent } from "@components/Dialog"; import { Form, InputLeading, TextAreaField, TextField } from "@components/form/fields"; import { Alert } from "@components/ui/Alert"; import Avatar from "@components/ui/Avatar"; 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"; // this describes the uniform data needed to create a new event type on Profile or Team interface EventTypeParent { teamId: number | null | undefined; // if undefined, then it's a profile name?: string | null; slug?: string | null; image?: string | null; } interface Props { // set true for use on the team settings page canAddEvents: boolean; // set true when in use on the team settings page isIndividualTeam?: boolean; // EventTypeParent can be a profile (as first option) or a team for the rest. options: EventTypeParent[]; } export default function CreateEventTypeButton(props: Props) { const { t } = useLocale(); const router = useRouter(); const modalOpen = useToggleQuery("new"); // URL encoded params const teamId: number | undefined = typeof router.query.teamId === "string" && router.query.teamId ? parseInt(router.query.teamId) : undefined; const pageSlug = router.query.eventPage || props.options[0].slug; const hasTeams = !!props.options.find((option) => option.teamId); const form = useForm<z.infer<typeof createEventTypeInput>>({ resolver: zodResolver(createEventTypeInput), defaultValues: { length: 15 }, }); const { setValue, watch, register } = form; useEffect(() => { const subscription = watch((value, { name, type }) => { if (name === "title" && type === "change") { if (value.title) setValue("slug", value.title.replace(/\s+/g, "-").toLowerCase()); else setValue("slug", ""); } }); return () => subscription.unsubscribe(); }, [watch, setValue]); const createMutation = trpc.useMutation("viewer.eventTypes.create", { onSuccess: async ({ eventType }) => { await router.push("/event-types/" + eventType.id); showToast(t("event_type_created_successfully", { eventTypeTitle: eventType.title }), "success"); }, onError: (err) => { if (err instanceof HttpError) { const message = `${err.statusCode}: ${err.message}`; showToast(message, "error"); } }, }); // inject selection data into url for correct router history const openModal = (option: EventTypeParent) => { // setTimeout fixes a bug where the url query params are removed immediately after opening the modal setTimeout(() => { router.push( { pathname: router.pathname, query: { ...router.query, new: "1", eventPage: option.slug, teamId: option.teamId || undefined, }, }, undefined, { shallow: true } ); }); }; // remove url params after close modal to reset state const closeModal = () => { router.replace({ pathname: router.pathname, query: { id: router.query.id || undefined }, }); }; return ( <Dialog open={modalOpen.isOn} onOpenChange={(isOpen) => { if (!isOpen) closeModal(); }}> {!hasTeams || props.isIndividualTeam ? ( <Button onClick={() => openModal(props.options[0])} data-testid="new-event-type" StartIcon={PlusIcon} {...(props.canAddEvents ? { href: modalOpen.hrefOn } : { disabled: true })}> {t("new_event_type_btn")} </Button> ) : ( <Dropdown> <DropdownMenuTrigger asChild> <Button EndIcon={ChevronDownIcon}>{t("new_event_type_btn")}</Button> </DropdownMenuTrigger> <DropdownMenuContent align="end"> <DropdownMenuLabel>{t("new_event_subtitle")}</DropdownMenuLabel> <DropdownMenuSeparator className="h-px bg-gray-200" /> {props.options.map((option) => ( <DropdownMenuItem key={option.slug} className="px-3 py-2 cursor-pointer hover:bg-neutral-100 focus:outline-none" onSelect={() => openModal(option)}> <Avatar alt={option.name || ""} imageSrc={option.image} size={6} className="inline mr-2" /> {option.name ? option.name : option.slug} </DropdownMenuItem> ))} </DropdownMenuContent> </Dropdown> )} <DialogContent> <div className="mb-4"> <h3 className="text-lg font-bold leading-6 text-gray-900" id="modal-title"> {teamId ? t("add_new_team_event_type") : t("add_new_event_type")} </h3> <div> <p className="text-sm text-gray-500">{t("new_event_type_to_book_description")}</p> </div> </div> <Form form={form} handleSubmit={(values) => { createMutation.mutate(values); }}> <div className="mt-3 space-y-4"> {teamId && ( <TextField type="hidden" labelProps={{ style: { display: "none" } }} {...register("teamId", { valueAsNumber: true })} value={teamId} /> )} <TextField label={t("title")} placeholder={t("quick_chat")} {...register("title")} /> <TextField label={t("url")} required addOnLeading={ <InputLeading> {process.env.NEXT_PUBLIC_APP_URL}/{pageSlug}/ </InputLeading> } {...register("slug")} /> <TextAreaField label={t("description")} placeholder={t("quick_video_meeting")} {...register("description")} /> <div className="relative"> <TextField type="number" required min="10" placeholder="15" defaultValue={15} label={t("length")} className="pr-20" {...register("length", { valueAsNumber: true })} /> <div className="absolute inset-y-0 right-0 flex items-center pt-4 mt-1.5 pr-3 text-sm text-gray-400"> {t("minutes")} </div> </div> {teamId && ( <div className="mb-4"> <label htmlFor="schedulingType" className="block text-sm font-bold text-gray-700"> {t("scheduling_type")} </label> {form.formState.errors.schedulingType && ( <Alert className="mt-1" severity="error" message={form.formState.errors.schedulingType.message} /> )} <RadioArea.Group {...register("schedulingType")} onChange={(val) => form.setValue("schedulingType", val as SchedulingType)} className="relative flex mt-1 space-x-6 rounded-sm shadow-sm"> <RadioArea.Item value={SchedulingType.COLLECTIVE} className="w-1/2 text-sm"> <strong className="block mb-1">{t("collective")}</strong> <p>{t("collective_description")}</p> </RadioArea.Item> <RadioArea.Item value={SchedulingType.ROUND_ROBIN} className="w-1/2 text-sm"> <strong className="block mb-1">{t("round_robin")}</strong> <p>{t("round_robin_description")}</p> </RadioArea.Item> </RadioArea.Group> </div> )} </div> <div className="flex flex-row-reverse mt-8 gap-x-2"> <Button type="submit" loading={createMutation.isLoading}> {t("continue")} </Button> <DialogClose asChild> <Button color="secondary">{t("cancel")}</Button> </DialogClose> </div> </Form> </DialogContent> </Dialog> ); }