diff --git a/apps/web/components/booking/AvailableEventLocations.tsx b/apps/web/components/booking/AvailableEventLocations.tsx index bcb61e982c..1d459f8dbc 100644 --- a/apps/web/components/booking/AvailableEventLocations.tsx +++ b/apps/web/components/booking/AvailableEventLocations.tsx @@ -8,54 +8,61 @@ import { FiLink } from "@calcom/ui/components/icon"; import type { Props } from "./pages/AvailabilityPage"; +const excludeNullValues = (value: unknown) => !!value; + export function AvailableEventLocations({ locations }: { locations: Props["eventType"]["locations"] }) { const { t } = useLocale(); - return locations.length ? ( -
- {locations.map((location, index) => { - const eventLocationType = getEventLocationType(location.type); - if (!eventLocationType) { - // It's possible that the location app got uninstalled - return null; - } + const renderLocations = locations.map((location, index) => { + const eventLocationType = getEventLocationType(location.type); + if (!eventLocationType) { + // It's possible that the location app got uninstalled + return null; + } + if (eventLocationType.variable === "hostDefault") { + return null; + } - const translateAbleKeys = [ - "attendee_in_person", - "in_person", - "attendee_phone_number", - "link_meeting", - "organizer_phone_number", - ]; + const translateAbleKeys = [ + "attendee_in_person", + "in_person", + "attendee_phone_number", + "link_meeting", + "organizer_phone_number", + ]; - const locationKey = z.string().default("").parse(locationKeyToString(location)); + const locationKey = z.string().default("").parse(locationKeyToString(location)); + const translatedLocation = location.type.startsWith("integrations:") + ? eventLocationType.label + : translateAbleKeys.includes(locationKey) + ? t(locationKey) + : locationKey; - const translatedLocation = location.type.startsWith("integrations:") - ? eventLocationType.label - : translateAbleKeys.includes(locationKey) - ? t(locationKey) - : locationKey; - - return ( -
- {eventLocationType.iconUrl === "/link.svg" ? ( - - ) : ( - {`${eventLocationType.label} + return ( +
+ {eventLocationType.iconUrl === "/link.svg" ? ( + + ) : ( + -

{translatedLocation}

- -
- ); - })} + alt={`${eventLocationType.label} icon`} + /> + )} + +

{translatedLocation}

+
+
+ ); + }); + + const filteredLocations = renderLocations.filter(excludeNullValues) as JSX.Element[]; + return filteredLocations.length ? ( +
+ {filteredLocations}
) : null; } diff --git a/apps/web/components/dialog/EditLocationDialog.tsx b/apps/web/components/dialog/EditLocationDialog.tsx index dfb2b443dd..d680371cef 100644 --- a/apps/web/components/dialog/EditLocationDialog.tsx +++ b/apps/web/components/dialog/EditLocationDialog.tsx @@ -33,6 +33,7 @@ interface ISetLocationDialog { saveLocation: (newLocationType: EventLocationType["type"], details: { [key: string]: string }) => void; selection?: LocationOption; booking?: BookingItem; + isTeamEvent?: boolean; defaultValues?: LocationObject[]; setShowLocationModal: React.Dispatch>; isOpenDialog: boolean; @@ -74,6 +75,7 @@ export const EditLocationDialog = (props: ISetLocationDialog) => { saveLocation, selection, booking, + isTeamEvent, setShowLocationModal, isOpenDialog, defaultValues, @@ -290,7 +292,9 @@ export const EditLocationDialog = (props: ISetLocationDialog) => { query={locationsQuery} success={({ data }) => { if (!data.length) return null; - const locationOptions = [...data]; + const locationOptions = [...data].filter((option) => { + return !isTeamEvent ? option.label !== "Conferencing" : true; + }); if (booking) { locationOptions.map((location) => location.options.filter((l) => !["phone", "attendeeInPerson"].includes(l.value)) diff --git a/apps/web/components/eventtype/EventSetupTab.tsx b/apps/web/components/eventtype/EventSetupTab.tsx index 3dc78075c9..49129ef9f6 100644 --- a/apps/web/components/eventtype/EventSetupTab.tsx +++ b/apps/web/components/eventtype/EventSetupTab.tsx @@ -35,16 +35,6 @@ const getLocationFromType = ( } }; -const getDefaultLocationValue = (options: EventTypeSetupProps["locationOptions"], type: string) => { - for (const locationType of options) { - for (const location of locationType.options) { - if (location.value === type && location.disabled === false) { - return location; - } - } - } -}; - export const EventSetupTab = ( props: Pick< EventTypeSetupProps, @@ -53,12 +43,16 @@ export const EventSetupTab = ( ) => { const { t } = useLocale(); const formMethods = useFormContext(); - const { eventType, locationOptions, team, destinationCalendar } = props; + const { eventType, team, destinationCalendar } = props; const [showLocationModal, setShowLocationModal] = useState(false); const [editingLocationType, setEditingLocationType] = useState(""); const [selectedLocation, setSelectedLocation] = useState(undefined); const [multipleDuration, setMultipleDuration] = useState(eventType.metadata.multipleDuration); + const locationOptions = props.locationOptions.filter((option) => { + return !team ? option.label !== "Conferencing" : true; + }); + 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 }), @@ -409,6 +403,7 @@ export const EventSetupTab = ( {/* We portal this modal so we can submit the form inside. Otherwise we get issues submitting two forms at once */} >; +} & Partial< + Record<"address" | "attendeeAddress" | "link" | "hostPhoneNumber" | "hostDefault" | "phone", string> +>; // integrations:jitsi | 919999999999 | Delhi | https://manual.meeting.link | Around Video export type BookingLocationValue = string; diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index 3f435a1c1f..71371d3a11 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -622,6 +622,7 @@ async function handler( let locationBodyString = location; let defaultLocationUrl = undefined; + if (dynamicUserList.length > 1) { users = users.sort((a, b) => { const aIndex = (a.username && dynamicUserList.indexOf(a.username)) || 0; @@ -701,6 +702,18 @@ async function handler( const [organizerUser] = users; const tOrganizer = await getTranslation(organizerUser?.locale ?? "en", "common"); + // use host default + if (isTeamEventType && locationBodyString === "conferencing") { + const metadataParseResult = userMetadataSchema.safeParse(organizerUser.metadata); + const organizerMetadata = metadataParseResult.success ? metadataParseResult.data : undefined; + if (organizerMetadata) { + const app = getAppFromSlug(organizerMetadata?.defaultConferencingApp?.appSlug); + locationBodyString = app?.appData?.location?.type || locationBodyString; + defaultLocationUrl = organizerMetadata?.defaultConferencingApp?.appLink; + } else { + locationBodyString = ""; + } + } const invitee = [ { diff --git a/packages/lib/defaultEvents.ts b/packages/lib/defaultEvents.ts index 1fcc4f9936..e76525b779 100644 --- a/packages/lib/defaultEvents.ts +++ b/packages/lib/defaultEvents.ts @@ -26,6 +26,7 @@ type UsernameSlugLinkProps = { }; const user: User = { + metadata: null, theme: null, credentials: [], username: "john.doe", diff --git a/packages/prisma/selects/user.ts b/packages/prisma/selects/user.ts index dcea2c8b60..e87f6a511d 100644 --- a/packages/prisma/selects/user.ts +++ b/packages/prisma/selects/user.ts @@ -45,6 +45,7 @@ export const userSelect = Prisma.validator()({ theme: true, brandColor: true, darkBrandColor: true, + metadata: true, ...availabilityUserSelect, }, });