diff --git a/apps/web/components/booking/AvailableEventLocations.tsx b/apps/web/components/booking/AvailableEventLocations.tsx new file mode 100644 index 0000000000..c46da9f75b --- /dev/null +++ b/apps/web/components/booking/AvailableEventLocations.tsx @@ -0,0 +1,32 @@ +import { getEventLocationType, locationKeyToString } from "@calcom/app-store/locations"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; + +import { Props } from "./pages/AvailabilityPage"; + +export function AvailableEventLocations({ locations }: { locations: Props["eventType"]["locations"] }) { + return ( +
+
+

+ {locations.map((location) => { + const eventLocationType = getEventLocationType(location.type); + if (!eventLocationType) { + // It's possible that the location app got uninstalled + return null; + } + return ( + + {`${eventLocationType.label} + {locationKeyToString(location)} + + ); + })} +

+
+
+ ); +} diff --git a/apps/web/components/booking/BookingListItem.tsx b/apps/web/components/booking/BookingListItem.tsx index 3bbc90570a..705eb91746 100644 --- a/apps/web/components/booking/BookingListItem.tsx +++ b/apps/web/components/booking/BookingListItem.tsx @@ -3,6 +3,7 @@ import { useRouter } from "next/router"; import { useState } from "react"; import { useMutation } from "react-query"; +import { EventLocationType, getEventLocationType } from "@calcom/app-store/locations"; import dayjs from "@calcom/dayjs"; import classNames from "@calcom/lib/classNames"; import { useLocale } from "@calcom/lib/hooks/useLocale"; @@ -17,8 +18,6 @@ import { TextArea } from "@calcom/ui/form/fields"; import { HttpError } from "@lib/core/http/error"; import useMeQuery from "@lib/hooks/useMeQuery"; -import { linkValueToString } from "@lib/linkValueToString"; -import { LocationType } from "@lib/location"; import { extractRecurringDates } from "@lib/parseDate"; import { EditLocationDialog } from "@components/dialog/EditLocationDialog"; @@ -182,17 +181,10 @@ function BookingListItem(booking: BookingItemProps) { }, }); - const saveLocation = (newLocationType: LocationType, details: { [key: string]: string }) => { + const saveLocation = (newLocationType: EventLocationType["type"], details: { [key: string]: string }) => { let newLocation = newLocationType as string; - if ( - newLocationType === LocationType.InPerson || - newLocationType === LocationType.Link || - newLocationType === LocationType.UserPhone || - newLocationType === LocationType.Riverside || - newLocationType === LocationType.Around || - newLocationType === LocationType.Whereby || - newLocationType === LocationType.Ping - ) { + const eventLocationType = getEventLocationType(newLocationType); + if (eventLocationType?.organizerInputType) { newLocation = details[Object.keys(details)[0]]; } setLocationMutation.mutate({ bookingId: booking.id, newLocation }); @@ -215,17 +207,7 @@ function BookingListItem(booking: BookingItemProps) { } } - let location = booking.location || ""; - - if (location.includes("integration")) { - if (booking.status === BookingStatus.CANCELLED || booking.status === BookingStatus.REJECTED) { - location = t("web_conference"); - } else if (isConfirmed) { - location = linkValueToString(booking.location, t); - } else { - location = t("web_conferencing_details_to_follow"); - } - } + const location = booking.location || ""; const onClick = () => { router.push({ diff --git a/apps/web/components/booking/pages/AvailabilityPage.tsx b/apps/web/components/booking/pages/AvailabilityPage.tsx index e213d5eedd..e876e118cf 100644 --- a/apps/web/components/booking/pages/AvailabilityPage.tsx +++ b/apps/web/components/booking/pages/AvailabilityPage.tsx @@ -8,7 +8,6 @@ import { useEffect, useMemo, useState } from "react"; import { FormattedNumber, IntlProvider } from "react-intl"; import { z } from "zod"; -import { AppStoreLocationType, LocationObject, LocationType } from "@calcom/app-store/locations"; import dayjs, { Dayjs } from "@calcom/dayjs"; import { useEmbedNonStylesConfig, @@ -46,41 +45,9 @@ import PoweredByCal from "@components/ui/PoweredByCal"; import type { AvailabilityPageProps } from "../../../pages/[user]/[type]"; import type { DynamicAvailabilityPageProps } from "../../../pages/d/[link]/[slug]"; import type { AvailabilityTeamPageProps } from "../../../pages/team/[slug]/[type]"; +import { AvailableEventLocations } from "../AvailableEventLocations"; -type Props = AvailabilityTeamPageProps | AvailabilityPageProps | DynamicAvailabilityPageProps; - -export const locationKeyToString = (location: LocationObject, t: TFunction) => { - switch (location.type) { - case LocationType.InPerson: - return location.address || "In Person"; // If disabled address won't exist on the object - case LocationType.Link: - case LocationType.Ping: - case LocationType.Riverside: - case LocationType.Around: - case LocationType.Whereby: - return location.link || "Link"; // If disabled link won't exist on the object - case LocationType.Phone: - return t("your_number"); - case LocationType.UserPhone: - return t("phone_call"); - case LocationType.GoogleMeet: - return "Google Meet"; - case LocationType.Zoom: - return "Zoom"; - case LocationType.Daily: - return "Cal Video"; - case LocationType.Jitsi: - return "Jitsi"; - case LocationType.Huddle01: - return "Huddle Video"; - case LocationType.Tandem: - return "Tandem"; - case LocationType.Teams: - return "Microsoft Teams"; - default: - return null; - } -}; +export type Props = AvailabilityTeamPageProps | AvailabilityPageProps | DynamicAvailabilityPageProps; const GoBackToPreviousPage = ({ t }: { t: TFunction }) => { const router = useRouter(); @@ -457,40 +424,9 @@ const AvailabilityPage = ({ profile, eventType }: Props) => { {t("requires_confirmation")}

)} - {eventType.locations.length === 1 && ( -

- {Object.values(AppStoreLocationType).includes( - eventType.locations[0].type as unknown as AppStoreLocationType - ) ? ( - - ) : ( - - )} - - {locationKeyToString(eventType.locations[0], t)} -

- )} - {eventType.locations.length > 1 && ( -
-
- -
-

- {eventType.locations.map((el, i, arr) => { - return ( - - {locationKeyToString(el, t)}{" "} - {arr.length - 1 !== i && ( - {t("or_lowercase")} - )} - - ); - })} -

-
- )} -

- + +

+ {eventType.length} {t("minutes")}

{eventType.price > 0 && ( @@ -590,40 +526,9 @@ const AvailabilityPage = ({ profile, eventType }: Props) => { {t("requires_confirmation")} )} - {eventType.locations.length === 1 && ( -

- {Object.values(AppStoreLocationType).includes( - eventType.locations[0].type as unknown as AppStoreLocationType - ) ? ( - - ) : ( - - )} - - {locationKeyToString(eventType.locations[0], t)} -

- )} - {eventType.locations.length > 1 && ( -
-
- -
-

- {eventType.locations.map((el, i, arr) => { - return ( - - {locationKeyToString(el, t)}{" "} - {arr.length - 1 !== i && ( - {t("or_lowercase")} - )} - - ); - })} -

-
- )} -

- + +

+ {eventType.length} {t("minutes")}

{!rescheduleUid && eventType.recurringEvent && ( diff --git a/apps/web/components/booking/pages/BookingPage.tsx b/apps/web/components/booking/pages/BookingPage.tsx index bff0cdb89d..f90a5f34d1 100644 --- a/apps/web/components/booking/pages/BookingPage.tsx +++ b/apps/web/components/booking/pages/BookingPage.tsx @@ -13,7 +13,14 @@ import { useMutation } from "react-query"; import { v4 as uuidv4 } from "uuid"; import { z } from "zod"; +import { + locationKeyToString, + getEventLocationValue, + getEventLocationType, + EventLocationType, +} from "@calcom/app-store/locations"; import { createPaymentLink } from "@calcom/app-store/stripepayment/lib/client"; +import { LocationObject, LocationType } from "@calcom/core/location"; import dayjs from "@calcom/dayjs"; import { useEmbedNonStylesConfig, @@ -37,7 +44,6 @@ import { EmailInput, Form } from "@calcom/ui/form/fields"; import { asStringOrNull } from "@lib/asStringOrNull"; import { timeZone } from "@lib/clock"; import { ensureArray } from "@lib/ensureArray"; -import { LocationObject, LocationType } from "@lib/location"; import createBooking from "@lib/mutations/bookings/create-booking"; import createRecurringBooking from "@lib/mutations/bookings/create-recurring-booking"; import { parseDate, parseRecurringDates } from "@lib/parseDate"; @@ -58,15 +64,13 @@ declare global { }; } -type BookingPageProps = (BookPageProps | TeamBookingPageProps | HashLinkPageProps) & { - locationLabels: Record; -}; +type BookingPageProps = BookPageProps | TeamBookingPageProps | HashLinkPageProps; type BookingFormValues = { name: string; email: string; notes?: string; - locationType?: LocationType; + locationType?: EventLocationType["type"]; guests?: string[]; phone?: string; hostPhoneNumber?: string; // Maybe come up with a better way to name this to distingish between two types of phone numbers @@ -83,7 +87,6 @@ const BookingPage = ({ profile, isDynamicGroupBooking, recurringEventCount, - locationLabels, hasHashedBookingLink, hashedLink, }: BookingPageProps) => { @@ -132,16 +135,6 @@ const BookingPage = ({ ); } - const location = (function humanReadableLocation(location) { - if (!location) { - return; - } - if (location.includes("integration")) { - return t("web_conferencing_details_to_follow"); - } - return location; - })(responseData.location); - return router.push({ pathname: "/success", query: { @@ -152,7 +145,7 @@ const BookingPage = ({ reschedule: !!rescheduleUid, name: attendees[0].name, email: attendees[0].email, - location, + location: responseData.location, eventName: profile.eventName || "", bookingId: id, isSuccessBookingPage: true, @@ -213,7 +206,6 @@ const BookingPage = ({ } }, [router.query.guest]); - const locationInfo = (type: LocationType) => locations.find((location) => location.type === type); const loggedInIsOwner = eventType?.users[0]?.id === session?.user?.id; const guestListEmails = !isDynamicGroupBooking ? booking?.attendees.slice(1).map((attendee) => attendee.email) @@ -296,12 +288,12 @@ const BookingPage = ({ resolver: zodResolver(bookingFormSchema), // Since this isn't set to strict we only validate the fields in the schema }); - const selectedLocation = useWatch({ + const selectedLocationType = useWatch({ control: bookingForm.control, name: "locationType", - defaultValue: ((): LocationType | undefined => { + defaultValue: ((): EventLocationType["type"] | undefined => { if (router.query.location) { - return router.query.location as LocationType; + return router.query.location as EventLocationType["type"]; } if (locations.length === 1) { return locations[0]?.type; @@ -309,40 +301,13 @@ const BookingPage = ({ })(), }); - const getLocationValue = ( - booking: Pick - ) => { - const { locationType } = booking; - switch (locationType) { - case LocationType.Phone: { - return booking.phone || ""; - } - case LocationType.InPerson: { - return locationInfo(locationType)?.address || ""; - } - case LocationType.Link: { - return locationInfo(locationType)?.link || ""; - } - case LocationType.UserPhone: { - return locationInfo(locationType)?.hostPhoneNumber || ""; - } - case LocationType.Around: { - return locationInfo(locationType)?.link || ""; - } - case LocationType.Riverside: { - return locationInfo(locationType)?.link || ""; - } - case LocationType.Whereby: { - return locationInfo(locationType)?.link || ""; - } - case LocationType.Ping: { - return locationInfo(locationType)?.link || ""; - } - // Catches all other location types, such as Google Meet, Zoom etc. - default: - return selectedLocation || ""; - } - }; + const selectedLocation = getEventLocationType(selectedLocationType); + const AttendeeInput = + selectedLocation?.attendeeInputType === "text" + ? "input" + : selectedLocation?.attendeeInputType === "phone" + ? PhoneInput + : null; // Calculate the booking date(s) let recurringStrings: string[] = [], @@ -403,9 +368,10 @@ const BookingPage = ({ language: i18n.language, rescheduleUid, user: router.query.user, - location: getLocationValue( - booking.locationType ? booking : { ...booking, locationType: selectedLocation } - ), + location: getEventLocationValue(locations, { + type: booking.locationType ? booking.locationType : selectedLocationType || "", + phone: booking.phone, + }), metadata, customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({ label: eventType.customInputs.find((input) => input.id === parseInt(inputId))?.label || "", @@ -414,7 +380,7 @@ const BookingPage = ({ hasHashedBookingLink, hashedLink, smsReminderNumber: - selectedLocation === LocationType.Phone ? booking.phone : booking.smsReminderNumber, + selectedLocationType === LocationType.Phone ? booking.phone : booking.smsReminderNumber, })); recurringMutation.mutate(recurringBookings); } else { @@ -430,9 +396,10 @@ const BookingPage = ({ rescheduleUid, bookingUid: router.query.bookingUid as string, user: router.query.user, - location: getLocationValue( - booking.locationType ? booking : { ...booking, locationType: selectedLocation } - ), + location: getEventLocationValue(locations, { + type: (booking.locationType ? booking.locationType : selectedLocationType) || "", + phone: booking.phone, + }), metadata, customInputs: Object.keys(booking.customInputs || {}).map((inputId) => ({ label: eventType.customInputs.find((input) => input.id === parseInt(inputId))?.label || "", @@ -441,13 +408,14 @@ const BookingPage = ({ hasHashedBookingLink, hashedLink, smsReminderNumber: - selectedLocation === LocationType.Phone ? booking.phone : booking.smsReminderNumber, + selectedLocationType === LocationType.Phone ? booking.phone : booking.smsReminderNumber, }); } }; // Should be disabled when rescheduleUid is present and data was found in defaultUserValues name/email fields. const disableInput = !!rescheduleUid && !!defaultUserValues.email && !!defaultUserValues.name; + const disableLocations = !!rescheduleUid; const disabledExceptForOwner = disableInput && !loggedInIsOwner; const inputClassName = "dark:placeholder:text-darkgray-600 focus:border-brand dark:border-darkgray-300 dark:text-darkgray-900 block w-full rounded-md border-gray-300 text-sm focus:ring-black disabled:bg-gray-200 disabled:hover:cursor-not-allowed dark:bg-transparent dark:selection:bg-green-500 disabled:dark:text-gray-500"; @@ -656,21 +624,34 @@ const BookingPage = ({ {t("location")} - {locations.map((location, i) => ( - - ))} + {locations.map((location, i) => { + const locationString = locationKeyToString(location); + // TODO: Right now selectedLocationType isn't send by getSSP. Once that's available defaultChecked should work and show the location in the original booking + const defaultChecked = rescheduleUid ? selectedLocationType === location.type : i === 0; + if (typeof locationString !== "string") { + // It's possible that location app got uninstalled + return null; + } + return ( + + ); + })} )} - {selectedLocation === LocationType.Phone && ( + {/* TODO: Change name and id ="phone" to something generic */} + {AttendeeInput && (