import { ErrorMessage } from "@hookform/error-message"; import { zodResolver } from "@hookform/resolvers/zod"; import { isValidPhoneNumber } from "libphonenumber-js"; import { Trans } from "next-i18next"; import Link from "next/link"; import { useEffect } from "react"; import { Controller, useForm, useWatch, useFormContext } from "react-hook-form"; import { z } from "zod"; import type { EventLocationType, LocationObject } from "@calcom/app-store/locations"; import { getEventLocationType, getHumanReadableLocationValue, getMessageForOrganizer, LocationType, OrganizerDefaultConferencingAppType, } from "@calcom/app-store/locations"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { RouterOutputs } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react"; import { Input } from "@calcom/ui"; import { Button, Dialog, DialogContent, DialogFooter, Form, PhoneInput } from "@calcom/ui"; import { MapPin } from "@calcom/ui/components/icon"; import { QueryCell } from "@lib/QueryCell"; import CheckboxField from "@components/ui/form/CheckboxField"; import type { LocationOption } from "@components/ui/form/LocationSelect"; import LocationSelect from "@components/ui/form/LocationSelect"; type BookingItem = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][number]; interface ISetLocationDialog { saveLocation: (newLocationType: EventLocationType["type"], details: { [key: string]: string }) => void; selection?: LocationOption; booking?: BookingItem; defaultValues?: LocationObject[]; setShowLocationModal: React.Dispatch>; isOpenDialog: boolean; setSelectedLocation?: (param: LocationOption | undefined) => void; setEditingLocationType?: (param: string) => void; teamId?: number; } const LocationInput = (props: { eventLocationType: EventLocationType; locationFormMethods: ReturnType; id: string; required: boolean; placeholder: string; className?: string; defaultValue?: string; }): JSX.Element | null => { const { eventLocationType, locationFormMethods, ...remainingProps } = props; const { control } = useFormContext() as typeof locationFormMethods; if (eventLocationType?.organizerInputType === "text") { return ( ); } else if (eventLocationType?.organizerInputType === "phone") { const { defaultValue, ...rest } = remainingProps; return ( { return ; }} /> ); } return null; }; export const EditLocationDialog = (props: ISetLocationDialog) => { const { saveLocation, selection, booking, setShowLocationModal, isOpenDialog, defaultValues, setSelectedLocation, setEditingLocationType, teamId, } = props; const { t } = useLocale(); const locationsQuery = trpc.viewer.locationOptions.useQuery({ teamId }); useEffect(() => { if (selection) { locationFormMethods.setValue("locationType", selection?.value); if (selection?.address) { locationFormMethods.setValue("locationAddress", selection?.address); } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [selection]); const locationFormSchema = z.object({ locationType: z.string(), phone: z.string().optional().nullable(), locationAddress: z.string().optional(), credentialId: z.number().optional(), teamName: z.string().optional(), locationLink: z .string() .optional() .superRefine((val, ctx) => { if ( eventLocationType && !eventLocationType.default && eventLocationType.linkType === "static" && eventLocationType.urlRegExp ) { const valid = z.string().regex(new RegExp(eventLocationType.urlRegExp)).safeParse(val).success; if (!valid) { const sampleUrl = eventLocationType.organizerInputPlaceholder; ctx.addIssue({ code: z.ZodIssueCode.custom, message: `Invalid URL for ${eventLocationType.label}. ${ sampleUrl ? `Sample URL: ${sampleUrl}` : "" }`, }); } return; } const valid = z.string().url().optional().safeParse(val).success; if (!valid) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: `Invalid URL`, }); } return; }), displayLocationPublicly: z.boolean().optional(), locationPhoneNumber: z .string() .nullable() .refine((val) => { if (val === null) return false; return isValidPhoneNumber(val); }) .optional(), }); const locationFormMethods = useForm({ mode: "onSubmit", resolver: zodResolver(locationFormSchema), }); const selectedLocation = useWatch({ control: locationFormMethods.control, name: "locationType", }); const selectedAddrValue = useWatch({ control: locationFormMethods.control, name: "locationAddress", }); const eventLocationType = getEventLocationType(selectedLocation); const defaultLocation = defaultValues?.find( (location: { type: EventLocationType["type"]; address?: string }) => { if (location.type === LocationType.InPerson) { return location.type === eventLocationType?.type && location.address === selectedAddrValue; } else { return location.type === eventLocationType?.type; } } ); const LocationOptions = (() => { if (eventLocationType && eventLocationType.organizerInputType && LocationInput) { if (!eventLocationType.variable) { console.error("eventLocationType.variable can't be undefined"); return null; } return (
{!booking && (
( locationFormMethods.setValue("displayLocationPublicly", e.target.checked) } informationIconText={t("display_location_info_badge")} /> )} />
)}
); } else { return

{getMessageForOrganizer(selectedLocation, t)}

; } })(); return ( setShowLocationModal(open)}>
{!booking && (

Can't find the right video app? Visit our App Store .

)}
{booking && ( <>

{t("current_location")}:

{getHumanReadableLocationValue(booking.location, t)}

)}
{ const { locationType: newLocation, displayLocationPublicly } = values; let details = {}; if (newLocation === LocationType.InPerson) { details = { address: values.locationAddress, }; } const eventLocationType = getEventLocationType(newLocation); // TODO: There can be a property that tells if it is to be saved in `link` if ( newLocation === LocationType.Link || (!eventLocationType?.default && eventLocationType?.linkType === "static") ) { details = { link: values.locationLink }; } if (newLocation === LocationType.UserPhone) { details = { hostPhoneNumber: values.locationPhoneNumber }; } if (eventLocationType?.organizerInputType) { details = { ...details, displayLocationPublicly, }; } if (values.credentialId) { details = { ...details, credentialId: values.credentialId, }; } if (values.teamName) { details = { ...details, teamName: values.teamName, }; } saveLocation(newLocation, details); setShowLocationModal(false); setSelectedLocation?.(undefined); locationFormMethods.unregister([ "locationType", "locationLink", "locationAddress", "locationPhoneNumber", ]); }}> { if (!data.length) return null; const locationOptions = [...data].map((option) => { if (teamId) { // Let host's Default conferencing App option show for Team Event return option; } return { ...option, options: option.options.filter((o) => o.value !== OrganizerDefaultConferencingAppType), }; }); if (booking) { locationOptions.map((location) => location.options.filter((l) => !["phone", "attendeeInPerson"].includes(l.value)) ); } return ( (
{ if (val) { locationFormMethods.setValue("locationType", val.value); if (val.credential) { locationFormMethods.setValue("credentialId", val.credential.id); locationFormMethods.setValue("teamName", val.credential.team?.name); } locationFormMethods.unregister([ "locationLink", "locationAddress", "locationPhoneNumber", ]); locationFormMethods.clearErrors([ "locationLink", "locationPhoneNumber", "locationAddress", ]); setSelectedLocation?.(val); } }} />
)} /> ); }} /> {selectedLocation && LocationOptions}
); };