diff --git a/apps/web/components/booking/pages/BookingPage.tsx b/apps/web/components/booking/pages/BookingPage.tsx index 478c83fc31..eab526bf66 100644 --- a/apps/web/components/booking/pages/BookingPage.tsx +++ b/apps/web/components/booking/pages/BookingPage.tsx @@ -40,6 +40,7 @@ import { getEveryFreqFor } from "@calcom/lib/recurringStrings"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry"; import { Icon } from "@calcom/ui/Icon"; import { Tooltip } from "@calcom/ui/Tooltip"; +import AddressInput from "@calcom/ui/form/AddressInputLazy"; import { Button } from "@calcom/ui/components"; import PhoneInput from "@calcom/ui/form/PhoneInputLazy"; import { EmailInput, Form } from "@calcom/ui/form/fields"; @@ -68,6 +69,8 @@ type BookingFormValues = { notes?: string; locationType?: EventLocationType["type"]; guests?: string[]; + address?: string; + attendeeAddress?: string; phone?: string; hostPhoneNumber?: string; // Maybe come up with a better way to name this to distingish between two types of phone numbers customInputs?: { @@ -269,6 +272,7 @@ const BookingPage = ({ .refine((val) => isValidPhoneNumber(val)) .optional() .nullable(), + attendeeAddress: z.string().optional().nullable(), smsReminderNumber: z .string() .refine((val) => isValidPhoneNumber(val)) @@ -297,10 +301,10 @@ const BookingPage = ({ const selectedLocation = getEventLocationType(selectedLocationType); const AttendeeInput = - selectedLocation?.attendeeInputType === "text" - ? "input" - : selectedLocation?.attendeeInputType === "phone" + selectedLocation?.attendeeInputType === "phone" ? PhoneInput + : selectedLocation?.attendeeInputType === "attendeeAddress" + ? AddressInput : null; // Calculate the booking date(s) @@ -356,6 +360,7 @@ const BookingPage = ({ location: getEventLocationValue(locations, { type: booking.locationType ? booking.locationType : selectedLocationType || "", phone: booking.phone, + attendeeAddress: booking.attendeeAddress, }), metadata, customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({ @@ -386,6 +391,7 @@ const BookingPage = ({ location: getEventLocationValue(locations, { type: (booking.locationType ? booking.locationType : selectedLocationType) || "", phone: booking.phone, + attendeeAddress: booking.attendeeAddress, }), metadata, customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({ @@ -648,16 +654,39 @@ const BookingPage = ({ {AttendeeInput && (
control={bookingForm.control} - name="phone" + bookingForm={bookingForm} + name={ + selectedLocationType === LocationType.Phone + ? "phone" + : selectedLocationType === LocationType.AttendeeInPerson + ? "attendeeAddress" + : "" + } placeholder={t(selectedLocation?.attendeeInputPlaceholder || "")} - id="phone" + id={ + selectedLocationType === LocationType.Phone + ? "phone" + : selectedLocationType === LocationType.AttendeeInPerson + ? "attendeeAddress" + : "" + } required disabled={disableInput} /> diff --git a/apps/web/components/dialog/EditLocationDialog.tsx b/apps/web/components/dialog/EditLocationDialog.tsx index 69410a1b45..12a6c868d7 100644 --- a/apps/web/components/dialog/EditLocationDialog.tsx +++ b/apps/web/components/dialog/EditLocationDialog.tsx @@ -293,7 +293,10 @@ export const EditLocationDialog = (props: ISetLocationDialog) => { defaultValue={selection} options={ booking - ? locationOptions.filter((location) => location.value !== "phone") + ? locationOptions.filter( + (location) => + location.value !== "phone" && location.value !== "attendeeInPerson" + ) : locationOptions } isSearchable diff --git a/apps/web/components/v2/eventtype/EditLocationDialog.tsx b/apps/web/components/v2/eventtype/EditLocationDialog.tsx index 7a1f6e19a2..c27f11ed4c 100644 --- a/apps/web/components/v2/eventtype/EditLocationDialog.tsx +++ b/apps/web/components/v2/eventtype/EditLocationDialog.tsx @@ -232,6 +232,12 @@ export const EditLocationDialog = (props: ISetLocationDialog) => { const { locationType: newLocation, displayLocationPublicly } = values; let details = {}; + if (newLocation === LocationType.AttendeeInPerson) { + details = { + address: values.locationAddress, + }; + } + if (newLocation === LocationType.InPerson) { details = { address: values.locationAddress, diff --git a/apps/web/pages/event-types/[type]/index.tsx b/apps/web/pages/event-types/[type]/index.tsx index 4b1fa3050b..647351ec2d 100644 --- a/apps/web/pages/event-types/[type]/index.tsx +++ b/apps/web/pages/event-types/[type]/index.tsx @@ -56,6 +56,7 @@ export type FormValues = { locations: { type: EventLocationType["type"]; address?: string; + attendeeAddress?: string; link?: string; hostPhoneNumber?: string; displayLocationPublicly?: boolean; diff --git a/packages/app-store/locations.ts b/packages/app-store/locations.ts index 5d90ea20ee..7c753113ab 100644 --- a/packages/app-store/locations.ts +++ b/packages/app-store/locations.ts @@ -16,8 +16,8 @@ export type DefaultEventLocationType = { iconUrl: string; // HACK: `variable` and `defaultValueVariable` are required due to legacy reason where different locations were stored in different places. - variable: "locationType" | "locationAddress" | "locationLink" | "locationPhoneNumber" | "phone"; - defaultValueVariable: "address" | "link" | "hostPhoneNumber" | "phone"; + variable: "locationType" | "locationAddress" | "address" | "locationLink" | "locationPhoneNumber" | "phone"; + defaultValueVariable: "address" | "attendeeAddress" | "link" | "hostPhoneNumber" | "phone"; } & ( | { organizerInputType: "phone" | "text" | null; @@ -26,7 +26,7 @@ export type DefaultEventLocationType = { attendeeInputPlaceholder?: null; } | { - attendeeInputType: "phone" | "text" | null; + attendeeInputType: "phone" | "attendeeAddress" | null; attendeeInputPlaceholder: string; organizerInputType?: null; organizerInputPlaceholder?: null; @@ -40,6 +40,13 @@ export type EventLocationType = DefaultEventLocationType | EventLocationTypeFrom export const DailyLocationType = "integrations:daily"; export enum DefaultEventLocationTypeEnum { + /** + * Booker Address + */ + AttendeeInPerson = "attendeeInPerson", + /** + * Organizer Address + */ InPerson = "inPerson", /** * Booker Phone @@ -53,10 +60,22 @@ export enum DefaultEventLocationTypeEnum { } export const defaultLocations: DefaultEventLocationType[] = [ + { + default: true, + type: DefaultEventLocationTypeEnum.AttendeeInPerson, + label: "In Person (Attendee Address)", + variable: "address", + organizerInputType: null, + messageForOrganizer: "Cal will ask your invitee to enter an address before scheduling.", + attendeeInputType: "attendeeAddress", + attendeeInputPlaceholder: `Enter Address`, + defaultValueVariable: "attendeeAddress", + iconUrl: "/map-pin.svg", + }, { default: true, type: DefaultEventLocationTypeEnum.InPerson, - label: "In Person", + label: "In Person (Organizer Address)", organizerInputType: "text", messageForOrganizer: "Provide an Address or Place", // HACK: @@ -103,7 +122,7 @@ export const defaultLocations: DefaultEventLocationType[] = [ export type LocationObject = { type: string; displayLocationPublicly?: boolean; -} & Partial>; +} & Partial>; // integrations:jitsi | 919999999999 | Delhi | https://manual.meeting.link | Around Video export type BookingLocationValue = string; diff --git a/packages/ui/form/AddressInput.tsx b/packages/ui/form/AddressInput.tsx new file mode 100644 index 0000000000..ebb1201d71 --- /dev/null +++ b/packages/ui/form/AddressInput.tsx @@ -0,0 +1,51 @@ +import { UseFormReturn } from "react-hook-form"; +import { Props } from "react-phone-number-input/react-hook-form"; + +import { EventLocationType } from "@calcom/app-store/locations"; + +import { Icon } from "../Icon"; + +type BookingFormValues = { + name: string; + email: string; + notes?: string; + locationType?: EventLocationType["type"]; + guests?: string[]; + address?: string; + attendeeAddress?: string; + phone?: string; + hostPhoneNumber?: string; // Maybe come up with a better way to name this to distingish between two types of phone numbers + customInputs?: { + [key: string]: string | boolean; + }; + rescheduleReason?: string; + smsReminderNumber?: string; +}; + +export type AddressInputProps = Props< + { + value: string; + id: string; + placeholder: string; + required: boolean; + bookingForm: UseFormReturn; + }, + FormValues +>; + +function AddressInput({ bookingForm, name, className, ...rest }: AddressInputProps) { + return ( +
+ + +
+ ); +} + +export default AddressInput; diff --git a/packages/ui/form/AddressInputLazy.tsx b/packages/ui/form/AddressInputLazy.tsx new file mode 100644 index 0000000000..ce2280d353 --- /dev/null +++ b/packages/ui/form/AddressInputLazy.tsx @@ -0,0 +1,8 @@ +import dynamic from "next/dynamic"; + +/** These are like 40kb that not every user needs */ +const AddressInput = dynamic( + () => import("./AddressInput") +) as unknown as typeof import("./AddressInput").default; + +export default AddressInput;