Guests will be able to add their own location for in person meetings (#5282)
* Guests will be able to add their own location or in person meetings * fix type error * fixed issue with attendees location not showing on success page * google meet booking will now go through, removed unnecessary change * filter out attendee address when editing location Co-authored-by: Peer Richelsen <peeroke@gmail.com>
This commit is contained in:
1 parent
a419613d29
commit
0fa7982084
7 files changed
+130
-13
No files matched your search
@@ -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 && (
|
||||
<div className="mb-4">
|
||||
<label
|
||||
htmlFor="phone"
|
||||
htmlFor={
|
||||
selectedLocationType === LocationType.Phone
|
||||
? "phone"
|
||||
: selectedLocationType === LocationType.AttendeeInPerson
|
||||
? "attendeeAddress"
|
||||
: ""
|
||||
}
|
||||
className="block text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("phone_number")}
|
||||
{selectedLocationType === LocationType.Phone
|
||||
? t("phone_number")
|
||||
: selectedLocationType === LocationType.AttendeeInPerson
|
||||
? t("Address")
|
||||
: ""}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<AttendeeInput<BookingFormValues>
|
||||
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}
|
||||
/>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -56,6 +56,7 @@ export type FormValues = {
|
||||
locations: {
|
||||
type: EventLocationType["type"];
|
||||
address?: string;
|
||||
attendeeAddress?: string;
|
||||
link?: string;
|
||||
hostPhoneNumber?: string;
|
||||
displayLocationPublicly?: boolean;
|
||||
|
||||
@@ -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<Record<"address" | "link" | "hostPhoneNumber" | "phone", string>>;
|
||||
} & Partial<Record<"address" | "attendeeAddress" | "link" | "hostPhoneNumber" | "phone", string>>;
|
||||
|
||||
// integrations:jitsi | 919999999999 | Delhi | https://manual.meeting.link | Around Video
|
||||
export type BookingLocationValue = string;
|
||||
|
||||
@@ -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<FormValues> = Props<
|
||||
{
|
||||
value: string;
|
||||
id: string;
|
||||
placeholder: string;
|
||||
required: boolean;
|
||||
bookingForm: UseFormReturn<BookingFormValues>;
|
||||
},
|
||||
FormValues
|
||||
>;
|
||||
|
||||
function AddressInput<FormValues>({ bookingForm, name, className, ...rest }: AddressInputProps<FormValues>) {
|
||||
return (
|
||||
<div className="relative ">
|
||||
<Icon.FiMapPin color="#D2D2D2" className="absolute top-1/2 left-0.5 ml-3 h-6 -translate-y-1/2" />
|
||||
<input
|
||||
{...rest}
|
||||
{...bookingForm.register("attendeeAddress")}
|
||||
name={name}
|
||||
color="#D2D2D2"
|
||||
className={`${className} border-1 focus-within:border-brand dark:bg-darkgray-100 dark:border-darkgray-300 block h-10 w-full rounded-md border border-gray-300 py-px pl-10 text-sm outline-none ring-black focus-within:ring-1 disabled:text-gray-500 disabled:opacity-50 dark:text-white dark:placeholder-gray-500 dark:selection:bg-green-500 disabled:dark:text-gray-500`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AddressInput;
|
||||
@@ -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;
|
||||
Reference in new issue
Block a user