diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index e0fa9b3b2e..bba640196a 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -3322,7 +3322,7 @@ "pbac_desc_delete_roles": "Delete roles", "pbac_desc_manage_roles": "All actions on roles across organization teams", "pbac_desc_create_workflows": "Create and set up new workflows", - "pbac_desc_view_workflows": "View existing workflows and their configurations", + "pbac_desc_view_workflows": "View existing workflows and their configurations", "pbac_desc_update_workflows": "Edit and modify workflow settings", "pbac_desc_delete_workflows": "Remove workflows from the system", "pbac_desc_manage_workflows": "Full management access to all workflows", @@ -3472,6 +3472,8 @@ "webhook_metadata": "Metadata", "stats": "Stats", "booking_status": "Booking status", + "location_custom_label_input_label": "Custom label on booking page", + "meeting_link": "Meeting link", "my_bookings": "My Bookings", "phone": "Phone", "ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑" diff --git a/packages/app-store/locations.ts b/packages/app-store/locations.ts index e7da1d8e15..d47194ab85 100644 --- a/packages/app-store/locations.ts +++ b/packages/app-store/locations.ts @@ -18,6 +18,7 @@ export type DefaultEventLocationType = { messageForOrganizer: string; category: "in person" | "conferencing" | "other" | "phone"; linkType: "static"; + supportsCustomLabel?: boolean; iconUrl: string; urlRegExp?: string; @@ -42,6 +43,7 @@ export type DefaultEventLocationType = { | { organizerInputType: "phone" | "text" | null; organizerInputPlaceholder?: string | null; + organizerInputLabel?: string | null; attendeeInputType?: null; attendeeInputPlaceholder?: null; } @@ -56,7 +58,7 @@ export type DefaultEventLocationType = { export type EventLocationTypeFromApp = Ensure< EventLocationTypeFromAppMeta, "defaultValueVariable" | "variable" ->; +> & { supportsCustomLabel?: boolean; organizerInputLabel?: string }; export type EventLocationType = DefaultEventLocationType | EventLocationTypeFromApp; @@ -147,18 +149,21 @@ export const defaultLocations: DefaultEventLocationType[] = [ category: "conferencing", messageForOrganizer: "", linkType: "static", + supportsCustomLabel: true, }, { default: true, type: DefaultEventLocationTypeEnum.Link, label: "link_meeting", organizerInputType: "text", + organizerInputLabel: "meeting_link", variable: "locationLink", messageForOrganizer: "Provide a Meeting Link", defaultValueVariable: "link", iconUrl: "/link.svg", category: "other", linkType: "static", + supportsCustomLabel: true, }, { default: true, @@ -182,6 +187,7 @@ export const defaultLocations: DefaultEventLocationType[] = [ label: "organizer_phone_number", messageForOrganizer: "Provide your phone number", organizerInputType: "phone", + organizerInputLabel: "phone_number", variable: "locationPhoneNumber", defaultValueVariable: "hostPhoneNumber", iconUrl: "/phone.svg", @@ -206,6 +212,7 @@ export type LocationObject = { address?: string; displayLocationPublicly?: boolean; credentialId?: number; + customLabel?: string; } & Partial< Record< "address" | "attendeeAddress" | "link" | "hostPhoneNumber" | "hostDefault" | "phone" | "somewhereElse", @@ -286,7 +293,7 @@ export const guessEventLocationType = (locationTypeOrValue: string | undefined | export const LocationType = { ...DefaultEventLocationTypeEnum, ...AppStoreLocationType }; -type PrivacyFilteredLocationObject = Optional; +type PrivacyFilteredLocationObject = Optional; export const privacyFilteredLocations = (locations: LocationObject[]): PrivacyFilteredLocationObject[] => { const locationsAfterPrivacyFilter = locations.map((location) => { diff --git a/packages/app-store/server.ts b/packages/app-store/server.ts index 291cb85b6b..30556d5f2e 100644 --- a/packages/app-store/server.ts +++ b/packages/app-store/server.ts @@ -26,6 +26,7 @@ export async function getLocationGroupedOptions( icon?: string; slug?: string; credentialId?: number; + supportsCustomLabel?: boolean; }[] > = {}; @@ -145,6 +146,7 @@ export async function getLocationGroupedOptions( label: l.label, value: l.type, icon: l.iconUrl, + supportsCustomLabel: l.supportsCustomLabel, }, ]; } else { @@ -153,6 +155,7 @@ export async function getLocationGroupedOptions( label: l.label, value: l.type, icon: l.iconUrl, + supportsCustomLabel: l.supportsCustomLabel, }, ]; } diff --git a/packages/features/bookings/components/event-meta/AvailableEventLocations.tsx b/packages/features/bookings/components/event-meta/AvailableEventLocations.tsx index 0b00ced567..1295d0edb1 100644 --- a/packages/features/bookings/components/event-meta/AvailableEventLocations.tsx +++ b/packages/features/bookings/components/event-meta/AvailableEventLocations.tsx @@ -7,9 +7,9 @@ import { getEventLocationType, getTranslatedLocation } from "@calcom/app-store/l import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import invertLogoOnDark from "@calcom/lib/invertLogoOnDark"; +import classNames from "@calcom/ui/classNames"; import { Icon } from "@calcom/ui/components/icon"; import { Tooltip } from "@calcom/ui/components/tooltip"; -import classNames from "@calcom/ui/classNames"; const excludeNullValues = (value: unknown) => !!value; @@ -47,7 +47,8 @@ function RenderLocationTooltip({ locations }: { locations: LocationObject[] }) { if (!eventLocationType) { return null; } - const translatedLocation = getTranslatedLocation(location, eventLocationType, t); + const translatedLocation = + location.customLabel || getTranslatedLocation(location, eventLocationType, t); return (
@@ -74,11 +75,8 @@ export function AvailableEventLocations({ locations }: { locations: LocationObje // It's possible that the location app got uninstalled return null; } - if (eventLocationType.variable === "hostDefault") { - return null; - } - const translatedLocation = getTranslatedLocation(location, eventLocationType, t); + const locationName = location?.customLabel || getTranslatedLocation(location, eventLocationType, t); return (
@@ -87,8 +85,8 @@ export function AvailableEventLocations({ locations }: { locations: LocationObje ) : ( )} - -

{translatedLocation}

+ +

{locationName}

); diff --git a/packages/features/bookings/lib/getLocationOptionsForSelect.ts b/packages/features/bookings/lib/getLocationOptionsForSelect.ts index 14ae49799a..00c6f6cac2 100644 --- a/packages/features/bookings/lib/getLocationOptionsForSelect.ts +++ b/packages/features/bookings/lib/getLocationOptionsForSelect.ts @@ -19,7 +19,7 @@ export default function getLocationsOptionsForSelect( return null; } const type = eventLocation.type; - const translatedLocation = getTranslatedLocation(location, eventLocation, t); + const translatedLocation = location.customLabel || getTranslatedLocation(location, eventLocation, t); return { // XYZ: is considered a namespace in i18next https://www.i18next.com/principles/namespaces and thus it gets cleaned up. diff --git a/packages/features/eventtypes/components/locations/DefaultLocationSettings.tsx b/packages/features/eventtypes/components/locations/DefaultLocationSettings.tsx new file mode 100644 index 0000000000..4cfd3452f2 --- /dev/null +++ b/packages/features/eventtypes/components/locations/DefaultLocationSettings.tsx @@ -0,0 +1,95 @@ +import { ErrorMessage } from "@hookform/error-message"; +import { useFieldArray, useFormContext } from "react-hook-form"; + +import { getEventLocationType } from "@calcom/app-store/locations"; +import type { LocationFormValues, FormValues } from "@calcom/features/eventtypes/lib/types"; +import CheckboxField from "@calcom/features/form/components/CheckboxField"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import classNames from "@calcom/ui/classNames"; +import { TextField } from "@calcom/ui/components/form"; + +import LocationInput from "./LocationInput"; +import LocationOptionContainer from "./LocationSettingsContainer"; +import type { LocationCustomClassNames } from "./types"; + +const DefaultLocationSettings = ({ + field, + index, + disableLocationProp, + customClassNames, +}: { + field: LocationFormValues["locations"][number]; + index: number; + disableLocationProp?: boolean; + customClassNames?: LocationCustomClassNames; +}) => { + const { t } = useLocale(); + const { getValues, control, formState } = useFormContext(); + const { update: updateLocationField } = useFieldArray({ + control, + name: "locations", + }); + const defaultLocation = field; + const eventLocationType = getEventLocationType(field.type); + + if (!eventLocationType) return null; + + return ( + + + + {eventLocationType.organizerInputType && ( + { + const fieldValues = getValues("locations")[index]; + updateLocationField(index, { + ...fieldValues, + displayLocationPublicly: e.target.checked, + }); + }} + informationIconText={t("display_location_info_badge")} + /> + )} + {eventLocationType?.supportsCustomLabel && ( + { + const fieldValues = getValues("locations")[index]; + updateLocationField(index, { + ...fieldValues, + customLabel: e.target.value, + }); + }} + /> + )} + + ); +}; + +export default DefaultLocationSettings; diff --git a/packages/features/eventtypes/components/locations/LocationInput.tsx b/packages/features/eventtypes/components/locations/LocationInput.tsx new file mode 100644 index 0000000000..b0485526df --- /dev/null +++ b/packages/features/eventtypes/components/locations/LocationInput.tsx @@ -0,0 +1,76 @@ +import { useFormContext, Controller } from "react-hook-form"; + +import type { EventLocationType } from "@calcom/app-store/locations"; +import PhoneInput from "@calcom/features/components/phone-input"; +import type { FormValues } from "@calcom/features/eventtypes/lib/types"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import classNames from "@calcom/ui/classNames"; +import { TextField } from "@calcom/ui/components/form"; + +import type { LocationInputCustomClassNames } from "./types"; + +const LocationInput = (props: { + eventLocationType: EventLocationType; + defaultValue?: string; + index: number; + customClassNames?: LocationInputCustomClassNames; + disableLocationProp?: boolean; + label?: string | null; +}) => { + const { t } = useLocale(); + const { eventLocationType, index, customClassNames, disableLocationProp, label, ...remainingProps } = props; + const formMethods = useFormContext(); + + if (eventLocationType?.organizerInputType === "text") { + const { defaultValue, ...rest } = remainingProps; + + return ( + { + return ( + + ); + }} + /> + ); + } else if (eventLocationType?.organizerInputType === "phone") { + const { defaultValue, ...rest } = remainingProps; + + return ( + { + return ( + + ); + }} + /> + ); + } + return null; +}; + +export default LocationInput; diff --git a/packages/features/eventtypes/components/locations/Locations.tsx b/packages/features/eventtypes/components/locations/Locations.tsx index cdd7003fad..c5fc7546e4 100644 --- a/packages/features/eventtypes/components/locations/Locations.tsx +++ b/packages/features/eventtypes/components/locations/Locations.tsx @@ -2,35 +2,32 @@ import { useAutoAnimate } from "@formkit/auto-animate/react"; import { ErrorMessage } from "@hookform/error-message"; import Link from "next/link"; import { useEffect, useState } from "react"; -import { Controller, useFieldArray, useFormContext } from "react-hook-form"; +import { useFieldArray, useFormContext } from "react-hook-form"; import type { UseFormGetValues, UseFormSetValue, Control, FormState } from "react-hook-form"; import type { EventLocationType } from "@calcom/app-store/locations"; import { getEventLocationType, MeetLocationType } from "@calcom/app-store/locations"; import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform"; -import PhoneInput from "@calcom/features/components/phone-input"; import type { LocationFormValues, EventTypeSetupProps, - CheckboxClassNames, FormValues, } from "@calcom/features/eventtypes/lib/types"; import CheckboxField from "@calcom/features/form/components/CheckboxField"; -import type { - LocationSelectCustomClassNames, - SingleValueLocationOption, -} from "@calcom/features/form/components/LocationSelect"; +import type { SingleValueLocationOption } from "@calcom/features/form/components/LocationSelect"; import LocationSelect from "@calcom/features/form/components/LocationSelect"; import ServerTrans from "@calcom/lib/components/ServerTrans"; import { WEBAPP_URL } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import classNames from "@calcom/ui/classNames"; import { Button } from "@calcom/ui/components/button"; -import { Input } from "@calcom/ui/components/form"; import { Icon } from "@calcom/ui/components/icon"; import { showToast } from "@calcom/ui/components/toast"; import CalVideoSettings from "./CalVideoSettings"; +import DefaultLocationSettings from "./DefaultLocationSettings"; +import LocationInput from "./LocationInput"; +import type { LocationCustomClassNames } from "./types"; export type TEventTypeLocation = Pick; export type TLocationOptions = Pick["locationOptions"]; @@ -42,19 +39,6 @@ type LocationInputCustomClassNames = { phoneInput?: string; }; -export type LocationCustomClassNames = { - container?: string; - locationSelect?: LocationSelectCustomClassNames; - removeLocationButton?: string; - removeLocationIcon?: string; - addLocationButton?: string; - organizerContactInput?: { - errorMessage?: string; - locationInput?: LocationInputCustomClassNames; - publicDisplayCheckbox?: CheckboxClassNames; - }; -}; - type LocationsProps = { team: { id: number } | null; destinationCalendar: TDestinationCalendar; @@ -107,66 +91,6 @@ const getLocationInfo = ({ return { locationAvailable, locationDetails }; }; -const LocationInput = (props: { - eventLocationType: EventLocationType; - defaultValue?: string; - index: number; - customClassNames?: LocationInputCustomClassNames; - disableLocationProp?: boolean; -}) => { - const { t } = useLocale(); - const { eventLocationType, index, customClassNames, disableLocationProp, ...remainingProps } = props; - if (eventLocationType?.organizerInputType === "text") { - const { defaultValue, ...rest } = remainingProps; - - return ( - { - return ( - - ); - }} - /> - ); - } else if (eventLocationType?.organizerInputType === "phone") { - const { defaultValue, ...rest } = remainingProps; - - return ( - { - return ( - - ); - }} - /> - ); - } - return null; -}; - const Locations: React.FC = ({ isChildrenManagedEventType, disableLocationProp, @@ -343,7 +267,14 @@ const Locations: React.FC = ({ {isCalVideo && !isPlatform && } - {eventLocationType?.organizerInputType && ( + {eventLocationType?.supportsCustomLabel && eventLocationType?.type ? ( + + ) : eventLocationType?.organizerInputType ? (
@@ -397,7 +328,7 @@ const Locations: React.FC = ({ />
- )} + ) : null} ); })} diff --git a/packages/features/eventtypes/components/locations/types.d.ts b/packages/features/eventtypes/components/locations/types.d.ts new file mode 100644 index 0000000000..1b5abd6828 --- /dev/null +++ b/packages/features/eventtypes/components/locations/types.d.ts @@ -0,0 +1,19 @@ +import type { LocationSelectCustomClassNames } from "@calcom/features/form/components/LocationSelect"; + +export type LocationCustomClassNames = { + container?: string; + locationSelect?: LocationSelectCustomClassNames; + removeLocationButton?: string; + removeLocationIcon?: string; + addLocationButton?: string; + organizerContactInput?: { + errorMessage?: string; + locationInput?: LocationInputCustomClassNames; + publicDisplayCheckbox?: CheckboxClassNames; + }; +}; + +export type LocationInputCustomClassNames = { + addressInput?: string; + phoneInput?: string; +}; diff --git a/packages/features/eventtypes/components/tabs/setup/EventSetupTab.tsx b/packages/features/eventtypes/components/tabs/setup/EventSetupTab.tsx index edda79f005..02bd706da1 100644 --- a/packages/features/eventtypes/components/tabs/setup/EventSetupTab.tsx +++ b/packages/features/eventtypes/components/tabs/setup/EventSetupTab.tsx @@ -26,8 +26,8 @@ import { Select } from "@calcom/ui/components/form"; import { SettingsToggle } from "@calcom/ui/components/form"; import { Skeleton } from "@calcom/ui/components/skeleton"; -import type { LocationCustomClassNames } from "../../locations/Locations"; import Locations from "../../locations/Locations"; +import type { LocationCustomClassNames } from "../../locations/types"; export type EventSetupTabCustomClassNames = { wrapper?: string; diff --git a/packages/features/eventtypes/lib/types.ts b/packages/features/eventtypes/lib/types.ts index cd74f47195..27d9057196 100644 --- a/packages/features/eventtypes/lib/types.ts +++ b/packages/features/eventtypes/lib/types.ts @@ -52,6 +52,7 @@ type EventLocation = { hostDefault?: string; credentialId?: number; teamName?: string; + customLabel?: string; }; type PhoneCallConfig = { diff --git a/packages/prisma/zod-utils.ts b/packages/prisma/zod-utils.ts index 3937a8aea0..53002f41a1 100644 --- a/packages/prisma/zod-utils.ts +++ b/packages/prisma/zod-utils.ts @@ -188,6 +188,7 @@ export const eventTypeLocations = z.array( hostPhoneNumber: z.string().optional(), credentialId: z.number().optional(), teamName: z.string().optional(), + customLabel: z.string().optional(), }) );