diff --git a/apps/web/components/eventtype/EventAdvancedTab.tsx b/apps/web/components/eventtype/EventAdvancedTab.tsx index 1e74f0bdf3..0de520f5d5 100644 --- a/apps/web/components/eventtype/EventAdvancedTab.tsx +++ b/apps/web/components/eventtype/EventAdvancedTab.tsx @@ -10,6 +10,7 @@ import type { EventNameObjectType } from "@calcom/core/event"; import { getEventName } from "@calcom/core/event"; import getLocationsOptionsForSelect from "@calcom/features/bookings/lib/getLocationOptionsForSelect"; import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector"; +import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager"; import { FormBuilder } from "@calcom/features/form-builder/FormBuilder"; import { classNames } from "@calcom/lib"; import { APP_NAME, CAL_URL } from "@calcom/lib/constants"; @@ -80,11 +81,19 @@ export const EventAdvancedTab = ({ eventType, team }: Pick setShowEventNameTip(false); const setEventName = (value: string) => formMethods.setValue("eventName", value); @@ -128,19 +137,19 @@ export const EventAdvancedTab = ({ eventType, team }: Pick setShowEventNameTip((old) => !old)} + size="sm" aria-label="edit custom name" - /> + className="hover:stroke-3 hover:text-emphasis min-w-fit px-0 !py-0 hover:bg-transparent" + onClick={() => setShowEventNameTip((old) => !old)}> + + } /> @@ -150,6 +159,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick
( onChange(e)} @@ -185,6 +197,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick { @@ -197,6 +210,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick { @@ -240,6 +255,10 @@ export const EventAdvancedTab = ({ eventType, team }: Pick } @@ -267,6 +284,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick{t("seats")}} onChange={(e) => { onChange(Math.abs(Number(e.target.value))); @@ -305,6 +325,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick formMethods.setValue("seatsShowAttendees", e.target.checked)} defaultChecked={!!eventType.seatsShowAttendees} /> diff --git a/apps/web/components/eventtype/EventAppsTab.tsx b/apps/web/components/eventtype/EventAppsTab.tsx index 2c7064c3c9..50dabb4abf 100644 --- a/apps/web/components/eventtype/EventAppsTab.tsx +++ b/apps/web/components/eventtype/EventAppsTab.tsx @@ -5,10 +5,11 @@ import type { GetAppData, SetAppData } from "@calcom/app-store/EventTypeAppConte import { EventTypeAppCard } from "@calcom/app-store/_components/EventTypeAppCardInterface"; import type { EventTypeAppCardComponentProps } from "@calcom/app-store/types"; import type { EventTypeAppsList } from "@calcom/app-store/utils"; +import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; -import { Button, EmptyScreen } from "@calcom/ui"; -import { Grid } from "@calcom/ui/components/icon"; +import { Button, EmptyScreen, Alert } from "@calcom/ui"; +import { Grid, Lock } from "@calcom/ui/components/icon"; export type EventType = Pick["eventType"] & EventTypeAppCardComponentProps["eventType"]; @@ -55,19 +56,39 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => { }; }; + const { shouldLockDisableProps, isManagedEventType, isChildrenManagedEventType } = useLockedFieldsManager( + eventType, + t("locked_fields_admin_description"), + t("locked_fields_member_description") + ); + return ( <>
+ {!installedApps?.length && isManagedEventType && ( + + )} {!isLoading && !installedApps?.length ? ( - {t("empty_installed_apps_button")}{" "} - + isChildrenManagedEventType && !isManagedEventType ? ( + + ) : ( + + ) } /> ) : null} @@ -82,22 +103,24 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => { ))}
-
- {!isLoading && notInstalledApps?.length ? ( -

Available Apps

- ) : null} -
- {notInstalledApps?.map((app) => ( - - ))} + {!shouldLockDisableProps("apps").disabled && ( +
+ {!isLoading && notInstalledApps?.length ? ( +

{t("available_apps")}

+ ) : null} +
+ {notInstalledApps?.map((app) => ( + + ))} +
-
+ )} ); }; diff --git a/apps/web/components/eventtype/AvailabilityTab.tsx b/apps/web/components/eventtype/EventAvailabilityTab.tsx similarity index 64% rename from apps/web/components/eventtype/AvailabilityTab.tsx rename to apps/web/components/eventtype/EventAvailabilityTab.tsx index 1dbab062b1..68240b3b82 100644 --- a/apps/web/components/eventtype/AvailabilityTab.tsx +++ b/apps/web/components/eventtype/EventAvailabilityTab.tsx @@ -1,29 +1,29 @@ -import type { FormValues } from "pages/event-types/[type]"; +import { SchedulingType } from "@prisma/client"; +import type { FormValues, EventTypeSetup } from "pages/event-types/[type]"; import { Controller, useFormContext } from "react-hook-form"; import type { OptionProps, SingleValueProps } from "react-select"; import { components } from "react-select"; import dayjs from "@calcom/dayjs"; +import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager"; import { NewScheduleButton } from "@calcom/features/schedules"; import classNames from "@calcom/lib/classNames"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { weekdayNames } from "@calcom/lib/weekday"; -import type { RouterOutputs } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react"; import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery"; import { Badge, Button, Select, SettingsToggle, SkeletonText, EmptyScreen } from "@calcom/ui"; import { ExternalLink, Globe, Clock } from "@calcom/ui/components/icon"; -import { SelectSkeletonLoader } from "@components/availability/SkeletonLoader"; - type AvailabilityOption = { label: string; value: number; isDefault: boolean; + isManaged?: boolean; }; const Option = ({ ...props }: OptionProps) => { - const { label, isDefault } = props.data; + const { label, isDefault, isManaged = false } = props.data; const { t } = useLocale(); return ( @@ -33,12 +33,17 @@ const Option = ({ ...props }: OptionProps) => { {t("default")} )} + {isManaged && ( + + {t("managed")} + + )} ); }; const SingleValue = ({ ...props }: SingleValueProps) => { - const { label, isDefault } = props.data; + const { label, isDefault, isManaged = false } = props.data; const { t } = useLocale(); return ( @@ -48,50 +53,39 @@ const SingleValue = ({ ...props }: SingleValueProps) => { {t("default")} )} + {isManaged && ( + + {t("managed")} + + )} ); }; const AvailabilitySelect = ({ className = "", - isLoading, - schedules, + options, + value, ...props }: { className?: string; name: string; - value: number; - isLoading: boolean; - schedules: RouterOutputs["viewer"]["availability"]["list"]["schedules"] | []; + value: AvailabilityOption | undefined; + options: AvailabilityOption[]; + isDisabled?: boolean; onBlur: () => void; onChange: (value: AvailabilityOption | null) => void; }) => { const { t } = useLocale(); - - if (isLoading) { - return ; - } - - const options = schedules.map((schedule) => ({ - value: schedule.id, - label: schedule.name, - isDefault: schedule.isDefault, - })); - - const value = options.find((option) => - props.value - ? option.value === props.value - : option.value === schedules.find((schedule) => schedule.isDefault)?.id - ); - return ( option.value === minimumBookingNoticeDisplayValues.type @@ -146,12 +140,30 @@ export const EventLimitsTab = ({ eventType }: Pick
- + { if (val) onChange(val.value); }} @@ -183,7 +196,10 @@ export const EventLimitsTab = ({ eventType }: Pick
- + { if (val) onChange(val.value); }} @@ -217,11 +234,20 @@ export const EventLimitsTab = ({ eventType }: Pick
- - + +
- + { formMethods.setValue("slotInterval", val && (val.value || 0) > 0 ? val.value : null); }} @@ -261,6 +288,7 @@ export const EventLimitsTab = ({ eventType }: Pick ( 0} onCheckedChange={(active) => { @@ -272,7 +300,12 @@ export const EventLimitsTab = ({ eventType }: Pick - + )} /> @@ -284,6 +317,7 @@ export const EventLimitsTab = ({ eventType }: Pick 0} onCheckedChange={(active) => { if (active) { @@ -297,6 +331,7 @@ export const EventLimitsTab = ({ eventType }: Pick @@ -311,13 +346,16 @@ export const EventLimitsTab = ({ eventType }: Pick formMethods.setValue("periodType", bool ? "ROLLING" : "UNLIMITED")}> formMethods.setValue("periodType", val as PeriodType)}> - {PERIOD_TYPES.map((period) => { + {PERIOD_TYPES.filter((opt) => + periodTypeLocked.disabled ? watchPeriodType === opt.type : true + ).map((period) => { if (period.type === "UNLIMITED") return null; return (
- - - + {!periodTypeLocked.disabled && ( + + + + )} {period.prefix ? {period.prefix}  : null} {period.type === "ROLLING" && ( -
- + - + option.value === limitKey)} onChange={onIntervalSelect} /> - {hasDeleteButton && ( + {hasDeleteButton && !disabled && (
@@ -450,6 +505,7 @@ type IntervalLimitsManagerProps = defaultLimit: number; step: number; textFieldSuffix?: string; + disabled?: boolean; }; const IntervalLimitsManager = ({ @@ -457,6 +513,7 @@ const IntervalLimitsManager = ({ defaultLimit, step, textFieldSuffix, + disabled, }: IntervalLimitsManagerProps) => { const { watch, setValue, control } = useFormContext(); const watchIntervalLimits = watch(propertyName); @@ -506,6 +563,7 @@ const IntervalLimitsManager = ({ limitKey={limitKey} step={step} value={value} + disabled={disabled} textFieldSuffix={textFieldSuffix} hasDeleteButton={Object.keys(currentIntervalLimits).length > 1} selectOptions={INTERVAL_LIMIT_OPTIONS.filter( @@ -536,7 +594,7 @@ const IntervalLimitsManager = ({ /> ); })} - {currentIntervalLimits && Object.keys(currentIntervalLimits).length <= 3 && ( + {currentIntervalLimits && Object.keys(currentIntervalLimits).length <= 3 && !disabled && ( diff --git a/apps/web/components/eventtype/EventRecurringTab.tsx b/apps/web/components/eventtype/EventRecurringTab.tsx index 2b41ca4679..6f72b9e6d3 100644 --- a/apps/web/components/eventtype/EventRecurringTab.tsx +++ b/apps/web/components/eventtype/EventRecurringTab.tsx @@ -11,7 +11,7 @@ export const EventRecurringTab = ({ eventType }: Pick - +
); }; diff --git a/apps/web/components/eventtype/EventSetupTab.tsx b/apps/web/components/eventtype/EventSetupTab.tsx index b7c7996dd3..57aa396573 100644 --- a/apps/web/components/eventtype/EventSetupTab.tsx +++ b/apps/web/components/eventtype/EventSetupTab.tsx @@ -11,6 +11,7 @@ import { z } from "zod"; import type { EventLocationType } from "@calcom/app-store/locations"; import { getEventLocationType, MeetLocationType, LocationType } from "@calcom/app-store/locations"; +import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager"; import { CAL_URL } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { md } from "@calcom/lib/markdownIt"; @@ -45,8 +46,33 @@ const getLocationFromType = ( } }; +const getLocationInfo = (props: Pick) => { + const locationAvailable = + props.eventType.locations && + props.eventType.locations.length > 0 && + props.locationOptions.some((op) => + op.options.find((opt) => opt.value === props.eventType.locations[0].type) + ); + const locationDetails = props.eventType.locations && + props.eventType.locations.length > 0 && + !locationAvailable && { + slug: props.eventType.locations[0].type + .replace("integrations:", "") + .replace(":", "-") + .replace("_video", ""), + name: props.eventType.locations[0].type + .replace("integrations:", "") + .replace(":", " ") + .replace("_video", "") + .split(" ") + .map((word) => word[0].toUpperCase() + word.slice(1)) + .join(" "), + }; + return { locationAvailable, locationDetails }; +}; interface DescriptionEditorProps { description?: string | null; + editable?: boolean; } const DescriptionEditor = (props: DescriptionEditorProps) => { @@ -64,6 +90,7 @@ const DescriptionEditor = (props: DescriptionEditorProps) => { setText={(value: string) => formMethods.setValue("description", turndown(value))} excludedToolbarItems={["blockType"]} placeholder={t("quick_video_meeting")} + editable={props.editable} /> ) : ( @@ -174,6 +201,13 @@ export const EventSetupTab = ( resolver: zodResolver(locationFormSchema), }); + const { isChildrenManagedEventType, isManagedEventType, shouldLockIndicator, shouldLockDisableProps } = + useLockedFieldsManager( + eventType, + t("locked_fields_admin_description"), + t("locked_fields_member_description") + ); + const Locations = () => { const { t } = useLocale(); @@ -188,6 +222,12 @@ export const EventSetupTab = ( return true; }); + const defaultValue = isManagedEventType + ? locationOptions.find((op) => op.label === t("default"))?.options[0] + : undefined; + + const { locationDetails, locationAvailable } = getLocationInfo(props); + return (
{validLocations.length === 0 && ( @@ -195,6 +235,8 @@ export const EventSetupTab = (
)} - {validLocations.length > 0 && ( + {isChildrenManagedEventType && !locationAvailable && locationDetails && ( +

+ {t("app_not_connected", { appName: locationDetails.name })}{" "} + + {t("connect_now")} + +

+ )} + {validLocations.length > 0 && !isManagedEventType && !isChildrenManagedEventType && (