import { useAutoAnimate } from "@formkit/auto-animate/react"; import { zodResolver } from "@hookform/resolvers/zod"; import { isValidPhoneNumber } from "libphonenumber-js"; import { Trans } from "next-i18next"; import Link from "next/link"; import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]"; import { useState } from "react"; import { Controller, useForm, useFormContext } from "react-hook-form"; import { MultiValue } from "react-select"; import { z } from "zod"; import { EventLocationType, getEventLocationType, MeetLocationType } from "@calcom/app-store/locations"; import { CAL_URL } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { Button, Label, Select, SettingsToggle, Skeleton, TextField } from "@calcom/ui"; import { FiEdit2, FiCheck, FiX, FiPlus } from "@calcom/ui/components/icon"; import { slugify } from "@lib/slugify"; import { EditLocationDialog } from "@components/dialog/EditLocationDialog"; import LocationSelect, { SingleValueLocationOption, LocationOption, } from "@components/ui/form/LocationSelect"; const getLocationFromType = ( type: EventLocationType["type"], locationOptions: Pick["locationOptions"] ) => { for (const locationOption of locationOptions) { const option = locationOption.options.find((option) => option.value === type); if (option) { return option; } } }; export const EventSetupTab = ( props: Pick ) => { const { t } = useLocale(); const formMethods = useFormContext(); const { eventType, locationOptions, team } = props; const [showLocationModal, setShowLocationModal] = useState(false); const [editingLocationType, setEditingLocationType] = useState(""); const [selectedLocation, setSelectedLocation] = useState(undefined); const [multipleDuration, setMultipleDuration] = useState(eventType.metadata.multipleDuration); const multipleDurationOptions = [5, 10, 15, 20, 25, 30, 45, 50, 60, 75, 80, 90, 120, 180].map((mins) => ({ value: mins, label: t("multiple_duration_mins", { count: mins }), })); const [selectedMultipleDuration, setSelectedMultipleDuration] = useState< MultiValue<{ value: number; label: string; }> >(multipleDurationOptions.filter((mdOpt) => multipleDuration?.includes(mdOpt.value))); const [defaultDuration, setDefaultDuration] = useState( selectedMultipleDuration.find((opt) => opt.value === eventType.length) ?? null ); const openLocationModal = (type: EventLocationType["type"]) => { const option = getLocationFromType(type, locationOptions); setSelectedLocation(option); setShowLocationModal(true); }; const removeLocation = (selectedLocation: typeof eventType.locations[number]) => { formMethods.setValue( "locations", formMethods.getValues("locations").filter((location) => location.type !== selectedLocation.type), { shouldValidate: true } ); }; const saveLocation = (newLocationType: EventLocationType["type"], details = {}) => { const locationType = editingLocationType !== "" ? editingLocationType : newLocationType; const existingIdx = formMethods.getValues("locations").findIndex((loc) => locationType === loc.type); if (existingIdx !== -1) { const copy = formMethods.getValues("locations"); if (editingLocationType !== "") { copy[existingIdx] = { ...details, type: newLocationType, }; } else { copy[existingIdx] = { ...formMethods.getValues("locations")[existingIdx], ...details, }; } formMethods.setValue("locations", copy); } else { formMethods.setValue( "locations", formMethods.getValues("locations").concat({ type: newLocationType, ...details }) ); } setEditingLocationType(""); setShowLocationModal(false); }; const locationFormSchema = z.object({ locationType: z.string(), locationAddress: z.string().optional(), displayLocationPublicly: z.boolean().optional(), locationPhoneNumber: z .string() .refine((val) => isValidPhoneNumber(val)) .optional(), locationLink: z.string().url().optional(), // URL validates as new URL() - which requires HTTPS:// In the input field }); const locationFormMethods = useForm<{ locationType: EventLocationType["type"]; locationPhoneNumber?: string; locationAddress?: string; // TODO: We should validate address or fetch the address from googles api to see if its valid? locationLink?: string; // Currently this only accepts links that are HTTPS:// displayLocationPublicly?: boolean; }>({ resolver: zodResolver(locationFormSchema), }); const Locations = () => { const { t } = useLocale(); const [animationRef] = useAutoAnimate(); const validLocations = formMethods.getValues("locations").filter((location) => { const eventLocation = getEventLocationType(location.type); if (!eventLocation) { // It's possible that the location app in use got uninstalled. return false; } return true; }); const defaultValue = locationOptions.find((item) => item.label === "video")?.options; return (
{validLocations.length === 0 && (
{ if (e?.value) { const newLocationType = e.value; const eventLocationType = getEventLocationType(newLocationType); if (!eventLocationType) { return; } locationFormMethods.setValue("locationType", newLocationType); if (eventLocationType.organizerInputType) { openLocationModal(newLocationType); } else { saveLocation(newLocationType); } } }} />
)} {validLocations.length > 0 && (
    {validLocations.map((location, index) => { const eventLocationType = getEventLocationType(location.type); if (!eventLocationType) { return null; } return (
  • {`${eventLocationType.label} {t(location[eventLocationType.defaultValueVariable] || eventLocationType.label)}
  • ); })} {validLocations.some((location) => location.type === MeetLocationType) && (

    The “Add to calendar” for this event type needs to be a Google Calendar for Meet to work. Change it{" "} here. {" "} We will fall back to Cal video if you do not change it.

    )} {validLocations.length > 0 && validLocations.length !== locationOptions.length && (
  • )}
)}
); }; return (
{CAL_URL?.replace(/^(https?:|)\/\//, "")}/ {team ? "team/" + team.slug : eventType.users[0].username}/ } {...formMethods.register("slug", { setValueAs: (v) => slugify(v), })} /> {multipleDuration ? (
{t("available_durations")} t("default_duration_no_options")} options={selectedMultipleDuration} onChange={(option) => { setDefaultDuration( selectedMultipleDuration.find((opt) => opt.value === option?.value) ?? null ); if (option) formMethods.setValue("length", option.value); }} />
) : ( {t("minutes")}} /> )}
{ if (multipleDuration !== undefined) { setMultipleDuration(undefined); formMethods.setValue("metadata.multipleDuration", undefined); formMethods.setValue("length", eventType.length); } else { setMultipleDuration([]); formMethods.setValue("metadata.multipleDuration", []); formMethods.setValue("length", 0); } }} />
{t("location")} } />
{/* We portal this modal so we can submit the form inside. Otherwise we get issues submitting two forms at once */}
); };