diff --git a/packages/app-store/routing-forms/__tests__/TestFormDialog.test.tsx b/packages/app-store/routing-forms/__tests__/TestFormDialog.test.tsx index 26f35885ec..30a2ff3738 100644 --- a/packages/app-store/routing-forms/__tests__/TestFormDialog.test.tsx +++ b/packages/app-store/routing-forms/__tests__/TestFormDialog.test.tsx @@ -161,20 +161,44 @@ describe("TestFormDialog", () => { }); it("renders the dialog when open", () => { - render( {}} />); + render( + { + return; + }} + /> + ); expect(screen.getByText("test_routing_form")).toBeInTheDocument(); expect(screen.getByText("test_preview_description")).toBeInTheDocument(); }); it("doesn't render the dialog when closed", () => { - render( {}} />); + render( + { + return; + }} + /> + ); expect(screen.queryByText("test_routing_form")).not.toBeInTheDocument(); }); it("renders form fields", () => { - render( {}} />); + render( + { + return; + }} + /> + ); expect(screen.getByTestId("form-field-name")).toBeInTheDocument(); }); @@ -182,7 +206,15 @@ describe("TestFormDialog", () => { describe("Team Form", () => { it("submits the form and shows test results for Custom Page", async () => { mockCustomPageMessageMatchingRoute(); - render( {}} />); + render( + { + return; + }} + /> + ); fireEvent.change(screen.getByTestId("form-field-name"), { target: { value: "John Doe" } }); fireEvent.click(screen.getByText("test_routing")); @@ -193,7 +225,15 @@ describe("TestFormDialog", () => { it("submits the form and shows test results for Event Type", async () => { mockEventTypeRedirectUrlMatchingRoute(); - render( {}} />); + render( + { + return; + }} + /> + ); fireEvent.change(screen.getByTestId("form-field-name"), { target: { value: "John Doe" } }); fireEvent.click(screen.getByText("test_routing")); expect(screen.getByText("route_to:")).toBeInTheDocument(); @@ -215,7 +255,15 @@ describe("TestFormDialog", () => { }, checkedFallback: false, }); - render( {}} />); + render( + { + return; + }} + /> + ); fireEvent.change(screen.getByTestId("form-field-name"), { target: { value: "John Doe" } }); fireEvent.click(screen.getByText("test_routing")); expect(screen.getByText("route_to:")).toBeInTheDocument(); @@ -237,7 +285,15 @@ describe("TestFormDialog", () => { mainWarnings: ["Main-Error-1", "Main-Error-2"], fallbackWarnings: ["Fallback-Error-1", "Fallback-Error-2"], }); - render( {}} />); + render( + { + return; + }} + /> + ); fireEvent.change(screen.getByTestId("form-field-name"), { target: { value: "John Doe" } }); fireEvent.click(screen.getByText("test_routing")); screen.logTestingPlaygroundURL(); @@ -255,7 +311,15 @@ describe("TestFormDialog", () => { mainWarnings: null, fallbackWarnings: null, }); - render( {}} />); + render( + { + return; + }} + /> + ); fireEvent.change(screen.getByTestId("form-field-name"), { target: { value: "John Doe" } }); fireEvent.click(screen.getByText("test_routing")); screen.logTestingPlaygroundURL(); @@ -273,7 +337,15 @@ describe("TestFormDialog", () => { mainWarnings: null, fallbackWarnings: null, }); - render( {}} />); + render( + { + return; + }} + /> + ); fireEvent.change(screen.getByTestId("form-field-name"), { target: { value: "John Doe" } }); fireEvent.click(screen.getByText("test_routing")); expect(screen.getByTestId("attribute-logic-matched")).toHaveTextContent("no"); diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index b233a7a618..fdb1932dda 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -1166,26 +1166,25 @@ async function handler( await usersRepository.updateLastActiveAt(booking.userId); } - // If it's a round robin event, record the reason for the host assignment - if (eventType.schedulingType === SchedulingType.ROUND_ROBIN) { - if (reqBody.crmOwnerRecordType && reqBody.crmAppSlug && contactOwnerEmail && routingFormResponseId) { - await AssignmentReasonRecorder.CRMOwnership({ - bookingId: booking.id, - crmAppSlug: reqBody.crmAppSlug, - teamMemberEmail: contactOwnerEmail, - recordType: reqBody.crmOwnerRecordType, - routingFormResponseId, - }); - } else if (routingFormResponseId && teamId) { - await AssignmentReasonRecorder.routingFormRoute({ - bookingId: booking.id, - routingFormResponseId, - organizerId: organizerUser.id, - teamId, - }); + // If it's a round robin event, record the reason for the host assignment + if (eventType.schedulingType === SchedulingType.ROUND_ROBIN) { + if (reqBody.crmOwnerRecordType && reqBody.crmAppSlug && contactOwnerEmail && routingFormResponseId) { + await AssignmentReasonRecorder.CRMOwnership({ + bookingId: booking.id, + crmAppSlug: reqBody.crmAppSlug, + teamMemberEmail: contactOwnerEmail, + recordType: reqBody.crmOwnerRecordType, + routingFormResponseId, + }); + } else if (routingFormResponseId && teamId) { + await AssignmentReasonRecorder.routingFormRoute({ + bookingId: booking.id, + routingFormResponseId, + organizerId: organizerUser.id, + teamId, + }); + } } - } - evt.uid = booking.uid ?? null; evt.oneTimePassword = booking.oneTimePassword ?? null; diff --git a/packages/features/eventtypes/components/tabs/availability/EventAvailabilityTab.tsx b/packages/features/eventtypes/components/tabs/availability/EventAvailabilityTab.tsx index 6972155334..0666dfedd5 100644 --- a/packages/features/eventtypes/components/tabs/availability/EventAvailabilityTab.tsx +++ b/packages/features/eventtypes/components/tabs/availability/EventAvailabilityTab.tsx @@ -16,6 +16,7 @@ import type { FormValues, EventTypeSetup, Host, + SelectClassNames, } from "@calcom/features/eventtypes/lib/types"; import classNames from "@calcom/lib/classNames"; import { useLocale } from "@calcom/lib/hooks/useLocale"; @@ -28,6 +29,42 @@ import { Spinner } from "@calcom/ui/components/icon/Spinner"; type ScheduleQueryData = RouterOutputs["viewer"]["availability"]["schedule"]["get"]; +type AvailabilityTableCustomClassNames = { + tableContainer?: string; + table?: string; + tableRow?: string; + day?: string; + dayUnavailable?: string; + dayAvailabilityContainer?: string; + dayAvailabilityFrom?: string; + dayAvailabilityTo?: string; + dayAvailabilitySeperator?: string; +}; + +type TeamMemmberScheduelCustomClassNames = SelectClassNames & { + labelAvatar?: string; + labelContainer?: string; +}; + +type UserAvailabilityCustomClassNames = { + availabilitySectionContainer?: string; + availabilitySelect?: SelectClassNames; + availabilityTable?: AvailabilityTableCustomClassNames; +}; + +type TeamAvailabilityCustomClassNames = { + chooseHostSchedulesLabelContainer?: string; + chooseHostSchedulesLabel?: string; + chooseHostSchedulesLabelDescription?: string; + teamAvailibilityContainer?: string; + teamMemberSchedule: TeamMemmberScheduelCustomClassNames; +}; + +export type EventAvailabilityTabCustomClassNames = { + teamAvailability: TeamAvailabilityCustomClassNames; + userAvailability: UserAvailabilityCustomClassNames; +}; + type EventTypeScheduleDetailsProps = { scheduleQueryData?: Pick & { schedule: Array>; @@ -35,6 +72,7 @@ type EventTypeScheduleDetailsProps = { isSchedulePending?: boolean; user?: Pick; editAvailabilityRedirectUrl?: string; + customClassNames?: AvailabilityTableCustomClassNames; }; type HostSchedulesQueryType = @@ -54,6 +92,8 @@ type HostSchedulesQueryType = type EventTypeTeamScheduleProps = { hostSchedulesQuery: HostSchedulesQueryType; + hosts?: Host[]; + customClassNames?: TeamAvailabilityCustomClassNames; }; type TeamMember = Pick; @@ -65,14 +105,22 @@ type EventTypeScheduleProps = { isSchedulesPending?: boolean; eventType: EventTypeSetup; teamMembers: TeamMember[]; -} & EventTypeScheduleDetailsProps & - EventTypeTeamScheduleProps; + customClassNames?: UserAvailabilityCustomClassNames; +} & Omit & + Omit; export type EventAvailabilityTabBaserProps = { isTeamEvent: boolean; }; -type EventAvailabilityTabProps = EventAvailabilityTabBaserProps & EventTypeScheduleProps; +type UseCommonScheduleSettingsToggle = Omit & { + customClassNames?: EventAvailabilityTabCustomClassNames; +}; + +type EventAvailabilityTabProps = EventAvailabilityTabBaserProps & + Omit & { + customClassNames?: EventAvailabilityTabCustomClassNames; + }; const Option = ({ ...props }: OptionProps) => { const { label, isDefault, isManaged = false } = props.data; @@ -127,6 +175,7 @@ const EventTypeScheduleDetails = memo( isSchedulePending, user, editAvailabilityRedirectUrl, + customClassNames, }: EventTypeScheduleDetailsProps) => { const timeFormat = user?.timeFormat; const { t, i18n } = useLocale(); @@ -138,16 +187,22 @@ const EventTypeScheduleDetails = memo( return (
-
-
    +
    +
      {weekdayNames(i18n.language, weekStart, "long").map((day, index) => { const isAvailable = !!filterDays(index).length; return ( -
    1. +
    2. {day} @@ -156,17 +211,33 @@ const EventTypeScheduleDetails = memo( ) : isAvailable ? (
      {filterDays(index).map((dayRange, i) => ( -
      - +
      + {format(dayRange.startTime, timeFormat === 12)} - - -
      {format(dayRange.endTime, timeFormat === 12)}
      + + - + +
      + {format(dayRange.endTime, timeFormat === 12)} +
      ))}
      ) : ( - {t("unavailable")} + + {t("unavailable")} + )}
    3. ); @@ -204,6 +275,7 @@ const EventTypeSchedule = ({ eventType, schedulesQueryData, isSchedulesPending, + customClassNames, ...rest }: EventTypeScheduleProps) => { const { t } = useLocale(); @@ -272,8 +344,17 @@ const EventTypeSchedule = ({ return (
      -
      -
      ); }; - const TeamMemberSchedule = ({ host, index, teamMembers, hostScheduleQuery, + customClassNames, }: { host: Host; index: number; teamMembers: TeamMember[]; hostScheduleQuery: HostSchedulesQueryType; + customClassNames?: TeamMemmberScheduelCustomClassNames; }) => { const { t } = useLocale(); const isPlatform = useIsPlatform(); @@ -355,10 +441,10 @@ const TeamMemberSchedule = ({ return ( <> -
      +
      {!isPlatform && } - {isPlatform && } -

      {label}

      + {isPlatform && } +

      {label}

      {isPending ? ( @@ -375,7 +461,11 @@ const TeamMemberSchedule = ({ onChange={(selected) => { field.onChange(selected?.value || null); }} - className="block w-full min-w-0 flex-1 rounded-sm text-sm" + className={classNames( + "block w-full min-w-0 flex-1 rounded-sm text-sm", + customClassNames?.select + )} + innerClassNames={customClassNames?.innerClassNames} value={value as AvailabilityOption} components={{ Option, SingleValue }} isMulti={false} @@ -393,17 +483,36 @@ const TeamMemberSchedule = ({ const TeamAvailability = ({ teamMembers, hostSchedulesQuery, -}: EventTypeTeamScheduleProps & { teamMembers: TeamMember[] }) => { + customClassNames, +}: EventTypeTeamScheduleProps & { + teamMembers: TeamMember[]; + customClassNames?: TeamAvailabilityCustomClassNames; +}) => { const { t } = useLocale(); const { watch } = useFormContext(); const [animationRef] = useAutoAnimate(); const hosts = watch("hosts"); return ( <> -
      -
      - -

      +

      +
      + +

      {t("hosts_schedule_description")}

      @@ -415,14 +524,16 @@ const TeamAvailability = ({ {hosts?.map((host, index) => (
    4. + className={classNames( + `flex flex-col px-3 py-2 ${index === hosts.length - 1 ? "" : "border-subtle border-b"}`, + customClassNames?.teamMemberSchedule?.container + )}>
    5. ))} @@ -457,7 +568,11 @@ const useCommonScheduleState = (initialScheduleId: number | null) => { }; }; -const UseCommonScheduleSettingsToggle = ({ eventType, ...rest }: EventTypeScheduleProps) => { +const UseCommonScheduleSettingsToggle = ({ + eventType, + customClassNames, + ...rest +}: UseCommonScheduleSettingsToggle) => { const { t } = useLocale(); const { useHostSchedulesForTeamEvent, toggleScheduleState } = useCommonScheduleState(eventType.schedule); return ( @@ -468,11 +583,19 @@ const UseCommonScheduleSettingsToggle = ({ eventType, ...rest }: EventTypeSchedu title={t("choose_common_schedule_team_event")} description={t("choose_common_schedule_team_event_description")}> {/* handles the state for which 'schedule' ID is set, as it's unknown until the Select dropdown is loaded */} - + {useHostSchedulesForTeamEvent && (
      - +
      )} @@ -483,6 +606,10 @@ export const EventAvailabilityTab = ({ eventType, isTeamEvent, ...rest }: EventA return isTeamEvent && eventType.schedulingType !== SchedulingType.MANAGED ? ( ) : ( - + ); }; diff --git a/packages/platform/atoms/event-types/wrappers/EventAvailabilityTabPlatformWrapper.tsx b/packages/platform/atoms/event-types/wrappers/EventAvailabilityTabPlatformWrapper.tsx index 5584df2d80..f882e0df71 100644 --- a/packages/platform/atoms/event-types/wrappers/EventAvailabilityTabPlatformWrapper.tsx +++ b/packages/platform/atoms/event-types/wrappers/EventAvailabilityTabPlatformWrapper.tsx @@ -1,5 +1,6 @@ import { useFormContext } from "react-hook-form"; +import type { EventAvailabilityTabCustomClassNames } from "@calcom/features/eventtypes/components/tabs/availability/EventAvailabilityTab"; import { EventAvailabilityTab } from "@calcom/features/eventtypes/components/tabs/availability/EventAvailabilityTab"; import type { EventTypeSetup, FormValues } from "@calcom/features/eventtypes/lib/types"; import type { User } from "@calcom/prisma/client"; @@ -16,6 +17,7 @@ type EventAvailabilityTabPlatformWrapperProps = { eventType: EventTypeSetup; isTeamEvent: boolean; teamId?: number; + customClassNames?: EventAvailabilityTabCustomClassNames; }; const EventAvailabilityTabPlatformWrapper = ({ diff --git a/packages/platform/atoms/event-types/wrappers/EventTypePlatformWrapper.tsx b/packages/platform/atoms/event-types/wrappers/EventTypePlatformWrapper.tsx index ea3924c960..783f3381eb 100644 --- a/packages/platform/atoms/event-types/wrappers/EventTypePlatformWrapper.tsx +++ b/packages/platform/atoms/event-types/wrappers/EventTypePlatformWrapper.tsx @@ -6,6 +6,7 @@ import { useRef, useState, useEffect } from "react"; import type { ChildrenEventType } from "@calcom/features/eventtypes/components/ChildrenEventTypeSelect"; import { EventType as EventTypeComponent } from "@calcom/features/eventtypes/components/EventType"; import ManagedEventTypeDialog from "@calcom/features/eventtypes/components/dialogs/ManagedEventDialog"; +import type { EventAvailabilityTabCustomClassNames } from "@calcom/features/eventtypes/components/tabs/availability/EventAvailabilityTab"; import type { EventRecurringTabCustomClassNames } from "@calcom/features/eventtypes/components/tabs/recurring/RecurringEventController"; import type { EventTypeSetupProps, FormValues, TabMap } from "@calcom/features/eventtypes/lib/types"; import { useLocale } from "@calcom/lib/hooks/useLocale"; @@ -30,6 +31,10 @@ import SetupTab from "./EventSetupTabPlatformWrapper"; import EventTeamAssignmentTabPlatformWrapper from "./EventTeamAssignmentTabPlatformWrapper"; export type PlatformTabs = keyof Omit; +export type eventTypeCustomClassNames = { + atomsWrapper?: string; + eventAvailabilityTab?: EventAvailabilityTabCustomClassNames; +}; export type EventTypeCustomClassNames = { atomsWrapper?: string; @@ -44,6 +49,7 @@ export type EventTypePlatformWrapperProps = { onDeleteSuccess?: () => void; onDeleteError?: (msg: string) => void; allowDelete: boolean; + customClassNames?: eventTypeCustomClassNames; customClassNames?: EventTypeCustomClassNames; disableToasts?: boolean; }; @@ -158,6 +164,7 @@ const EventType = ({ isTeamEvent={!!team} user={user?.data} teamId={team?.id} + customClassNames={customClassNames?.eventAvailabilityTab} /> ) : ( <> diff --git a/packages/platform/examples/base/src/pages/event-types.tsx b/packages/platform/examples/base/src/pages/event-types.tsx index a20b070810..c9261d2e4f 100644 --- a/packages/platform/examples/base/src/pages/event-types.tsx +++ b/packages/platform/examples/base/src/pages/event-types.tsx @@ -97,6 +97,60 @@ export default function Bookings(props: { calUsername: string; calEmail: string