From eae74c568cd8ad99d322d49be802bfa6c3d34c50 Mon Sep 17 00:00:00 2001 From: Somay Chauhan Date: Mon, 25 Nov 2024 12:11:27 +0530 Subject: [PATCH] feat: added custom classnames for event-advanced tab (#17479) * feat: added custom classnames to event-advanced tab * added more calssnames * added classNames to common types * undo extra changes * Update EventTypePlatformWrapper.tsx * Update types.ts * Update EventTypePlatformWrapper.tsx * better classnames * Update EventTypePlatformWrapper.tsx * addeed CustomEventTypeModalClassNames * added example classnames for demo and some changes * fix: better classnames * timezoneLockToggle -> timezoneLock * Update types.ts * Update CustomEventTypeModal.tsx --- .../calendars/DestinationCalendarSelector.tsx | 7 +- .../tabs/advanced/CustomEventTypeModal.tsx | 203 +++++++++++---- .../tabs/advanced/DisableAllEmailsSetting.tsx | 55 +++- .../tabs/advanced/EventAdvancedTab.tsx | 239 +++++++++++++++--- .../RequiresConfirmationController.tsx | 48 +++- .../wrappers/EventTypePlatformWrapper.tsx | 3 + .../examples/base/src/pages/event-types.tsx | 149 +++++++++++ 7 files changed, 591 insertions(+), 113 deletions(-) diff --git a/packages/features/calendars/DestinationCalendarSelector.tsx b/packages/features/calendars/DestinationCalendarSelector.tsx index 082aa4e74f..a619dd3d47 100644 --- a/packages/features/calendars/DestinationCalendarSelector.tsx +++ b/packages/features/calendars/DestinationCalendarSelector.tsx @@ -3,6 +3,7 @@ import { useEffect, useState } from "react"; import type { OptionProps, SingleValueProps } from "react-select"; import { components } from "react-select"; +import type { SelectClassNames } from "@calcom/features/eventtypes/lib/types"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { RouterOutputs } from "@calcom/trpc/react"; import { Badge, Icon, Select } from "@calcom/ui"; @@ -16,6 +17,7 @@ interface Props { maxWidth?: number; hideAdvancedText?: boolean; calendarsQueryData?: RouterOutputs["viewer"]["connectedCalendars"]; + customClassNames?: SelectClassNames; } interface Option { @@ -53,6 +55,7 @@ const DestinationCalendarSelector = ({ hideAdvancedText, maxWidth, calendarsQueryData, + customClassNames, }: Props): JSX.Element | null => { const { t } = useLocale(); const connectedCalendarsList = calendarsQueryData?.connectedCalendars; @@ -157,8 +160,10 @@ const DestinationCalendarSelector = ({ }} isSearchable={false} className={classNames( - "border-default my-2 block w-full min-w-0 flex-1 rounded-none rounded-r-sm text-sm" + "border-default my-2 block w-full min-w-0 flex-1 rounded-none rounded-r-sm text-sm", + customClassNames?.select )} + innerClassNames={customClassNames?.innerClassNames} onChange={(newValue) => { setSelectedOption(newValue); if (!newValue) { diff --git a/packages/features/eventtypes/components/tabs/advanced/CustomEventTypeModal.tsx b/packages/features/eventtypes/components/tabs/advanced/CustomEventTypeModal.tsx index 05efd11671..e569fa6587 100644 --- a/packages/features/eventtypes/components/tabs/advanced/CustomEventTypeModal.tsx +++ b/packages/features/eventtypes/components/tabs/advanced/CustomEventTypeModal.tsx @@ -6,6 +6,8 @@ import { useForm, useFormContext } from "react-hook-form"; import type { EventNameObjectType } from "@calcom/core/event"; import { getEventName } from "@calcom/core/event"; import { validateCustomEventName } from "@calcom/core/event"; +import type { InputClassNames } from "@calcom/features/eventtypes/lib/types"; +import { classNames } from "@calcom/lib"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { Button, Dialog, DialogClose, DialogFooter, DialogContent, TextField } from "@calcom/ui"; @@ -13,6 +15,35 @@ interface FormValues { customEventName: string; } +export type CustomEventTypeModalClassNames = { + eventNameInput?: InputClassNames; + availableVariables?: { + container?: string; + header?: string; + scrollArea?: string; + variableItem?: { + container?: string; + variableName?: string; + variableDescription?: string; + }; + bookingFieldsHeader?: string; + bookingFieldItem?: { + container?: string; + fieldName?: string; + fieldValue?: string; + }; + }; + previewSection?: { + header?: string; + previewContainer?: string; + previewEventBox?: { + container?: string; + eventName?: string; + eventTime?: string; + }; + }; +}; + interface CustomEventTypeModalFormProps { placeHolder: string; close: () => void; @@ -20,18 +51,18 @@ interface CustomEventTypeModalFormProps { event: EventNameObjectType; defaultValue: string; isNameFieldSplit: boolean; + customClassNames?: CustomEventTypeModalClassNames; } const CustomEventTypeModalForm: FC = (props) => { const { t } = useLocale(); - const { placeHolder, close, setValue, event, isNameFieldSplit } = props; + const { placeHolder, close, setValue, event, isNameFieldSplit, customClassNames } = props; const { register, handleSubmit, watch, getValues } = useFormContext(); const onSubmit: SubmitHandler = (data) => { setValue(data.customEventName); close(); }; - // const customEventName = watch("customEventName"); const previewText = getEventName({ ...event, eventName: watch("customEventName") }); const placeHolder_ = watch("customEventName") === "" ? previewText : placeHolder; @@ -45,7 +76,6 @@ const CustomEventTypeModalForm: FC = (props) => { if (isEmpty) { setValue(""); } - handleSubmit(onSubmit)(e); }}> = (props) => { : validationResult; }, })} - className="mb-0" + containerClassName={customClassNames?.eventNameInput?.container} + labelClassName={customClassNames?.eventNameInput?.label} + className={classNames("mb-0", customClassNames?.eventNameInput?.input)} />
-
-

{t("available_variables")}

-
-
-

{`{Event type title}`}

-

{t("event_name_info")}

+
+

+ {t("available_variables")} +

+
+
+

+ {`{Event type title}`} +

+

+ {t("event_name_info")} +

-
-

{`{Event duration}`}

-

{t("event_duration_info")}

-
-
-

{`{Organiser}`}

-

{t("your_full_name")}

-
-
-

{`{Organiser first name}`}

-

{t("organizer_first_name")}

-
-
-

{`{Scheduler}`}

-

{t("scheduler_full_name")}

-
- -
-

{`{Scheduler first name}`}

-

{t("scheduler_first_name")}

-
- - {isNameFieldSplit && ( -
-

{`{Scheduler last name}`}

-

{t("scheduler_last_name")}

-
- )} -
-

{`{Location}`}

-

{t("location_info")}

+
+

+ {`{Event duration}`} +

+

+ {t("event_duration_info")} +

+ {/* Additional variable items here */} {event.bookingFields && ( -

{t("booking_question_response_variables")}

+

+ {t("booking_question_response_variables")} +

)} {event.bookingFields && Object.keys(event.bookingFields).map((bookingfield, index) => ( -
-

{`{${bookingfield}}`}

-

+

+

+ {`{${bookingfield}}`} +

+

{event.bookingFields?.[bookingfield]?.toString()}

))}
-

{t("preview")}

+

+ {t("preview")} +

-
-

{previewText}

-

8 - 10 AM

+
+

+ {previewText} +

+

+ 8 - 10 AM +

@@ -141,12 +238,12 @@ interface CustomEventTypeModalProps { setValue: (value: string) => void; event: EventNameObjectType; isNameFieldSplit: boolean; + customClassNames?: CustomEventTypeModalClassNames; } const CustomEventTypeModal: FC = (props) => { const { t } = useLocale(); - - const { defaultValue, placeHolder, close, setValue, event, isNameFieldSplit } = props; + const { defaultValue, placeHolder, close, setValue, event, isNameFieldSplit, customClassNames } = props; const methods = useForm({ defaultValues: { @@ -169,11 +266,11 @@ const CustomEventTypeModal: FC = (props) => { placeHolder={placeHolder} defaultValue={defaultValue} isNameFieldSplit={isNameFieldSplit} + customClassNames={customClassNames} /> {t("cancel")} - diff --git a/packages/features/eventtypes/components/tabs/advanced/DisableAllEmailsSetting.tsx b/packages/features/eventtypes/components/tabs/advanced/DisableAllEmailsSetting.tsx index fe41a90238..ccf2d82d80 100644 --- a/packages/features/eventtypes/components/tabs/advanced/DisableAllEmailsSetting.tsx +++ b/packages/features/eventtypes/components/tabs/advanced/DisableAllEmailsSetting.tsx @@ -2,6 +2,8 @@ import type { TFunction } from "next-i18next"; import { Trans } from "next-i18next"; import { useState } from "react"; +import type { InputClassNames, SettingsToggleClassNames } from "@calcom/features/eventtypes/lib/types"; +import { classNames } from "@calcom/lib"; import { SettingsToggle, Dialog, @@ -12,11 +14,26 @@ import { Button, } from "@calcom/ui"; +export type EmailNotificationToggleCustomClassNames = SettingsToggleClassNames & { + confirmationDialog?: { + container?: string; + dialogTitle?: string; + description?: string; + confirmInput?: InputClassNames; + dialogFooter?: { + container?: string; + confirmButton?: string; + cancelButton?: string; + }; + }; +}; + interface DisableEmailsSettingProps { checked: boolean; onCheckedChange: (e: boolean) => void; recipient: "attendees" | "hosts"; t: TFunction; + customClassNames?: EmailNotificationToggleCustomClassNames; } export const DisableAllEmailsSetting = ({ @@ -24,6 +41,7 @@ export const DisableAllEmailsSetting = ({ onCheckedChange, recipient, t, + customClassNames, }: DisableEmailsSettingProps) => { const [dialogOpen, setDialogOpen] = useState(false); const [confirmText, setConfirmText] = useState(""); @@ -34,8 +52,12 @@ export const DisableAllEmailsSetting = ({ return (
setDialogOpen(e)}> - -

+ +

This will disable all emails to {recipient}. This includes booking confirmations, requests, reschedules and reschedule requests, cancellation emails, and any other emails related to @@ -46,36 +68,45 @@ export const DisableAllEmailsSetting = ({ to their bookings.

-

{t("type_confirm_to_continue")}

+

+ {t("type_confirm_to_continue")} +

{ setConfirmText(e.target.value); }} /> - - + +
{ - checked ? onCheckedChange(!checked) : setDialogOpen(true); - }} + onCheckedChange={() => (checked ? onCheckedChange(!checked) : setDialogOpen(true))} />
); diff --git a/packages/features/eventtypes/components/tabs/advanced/EventAdvancedTab.tsx b/packages/features/eventtypes/components/tabs/advanced/EventAdvancedTab.tsx index fbf65ee3a0..0783ad1117 100644 --- a/packages/features/eventtypes/components/tabs/advanced/EventAdvancedTab.tsx +++ b/packages/features/eventtypes/components/tabs/advanced/EventAdvancedTab.tsx @@ -13,7 +13,14 @@ import { allowDisablingHostConfirmationEmails, } from "@calcom/features/ee/workflows/lib/allowDisablingStandardEmails"; import { MultiplePrivateLinksController } from "@calcom/features/eventtypes/components"; -import type { FormValues, EventTypeSetupProps } from "@calcom/features/eventtypes/lib/types"; +import type { + FormValues, + EventTypeSetupProps, + SelectClassNames, + CheckboxClassNames, + InputClassNames, + SettingsToggleClassNames, +} from "@calcom/features/eventtypes/lib/types"; import { FormBuilder } from "@calcom/features/form-builder/FormBuilder"; import type { fieldSchema } from "@calcom/features/form-builder/schema"; import type { EditableSchema } from "@calcom/features/form-builder/schema"; @@ -41,10 +48,48 @@ import { ColorPicker, } from "@calcom/ui"; +import type { CustomEventTypeModalClassNames } from "./CustomEventTypeModal"; import CustomEventTypeModal from "./CustomEventTypeModal"; +import type { EmailNotificationToggleCustomClassNames } from "./DisableAllEmailsSetting"; import { DisableAllEmailsSetting } from "./DisableAllEmailsSetting"; +import type { RequiresConfirmationCustomClassNames } from "./RequiresConfirmationController"; import RequiresConfirmationController from "./RequiresConfirmationController"; +export type EventAdvancedTabCustomClassNames = { + destinationCalendar?: SelectClassNames; + eventName?: InputClassNames; + customEventTypeModal?: CustomEventTypeModalClassNames; + addToCalendarEmailOrganizer?: SettingsToggleClassNames & { + emailSelect?: { + container?: string; + select?: string; + displayEmailLabel?: string; + }; + }; + requiresConfirmation?: RequiresConfirmationCustomClassNames; + bookerEmailVerification?: SettingsToggleClassNames; + calendarNotes?: SettingsToggleClassNames; + eventDetailsVisibility?: SettingsToggleClassNames; + bookingRedirect?: SettingsToggleClassNames & { + children?: string; + redirectUrlInput?: InputClassNames; + forwardParamsCheckbox?: CheckboxClassNames; + error?: string; + }; + seatsOptions?: SettingsToggleClassNames & { + children?: string; + showAttendeesCheckbox?: CheckboxClassNames; + showAvalableSeatCountCheckbox?: CheckboxClassNames; + seatsInput: InputClassNames; + }; + timezoneLock?: SettingsToggleClassNames; + eventTypeColors?: SettingsToggleClassNames & { + warningText?: string; + }; + roundRobinReschedule?: SettingsToggleClassNames; + emailNotifications?: EmailNotificationToggleCustomClassNames; +}; + type BookingField = z.infer; export type EventAdvancedBaseProps = Pick & { @@ -53,6 +98,7 @@ export type EventAdvancedBaseProps = Pick; isUserLoading?: boolean; showToast: (message: string, variant: "success" | "warning" | "error") => void; + customClassNames?: EventAdvancedTabCustomClassNames; }; export type EventAdvancedTabProps = EventAdvancedBaseProps & { @@ -68,6 +114,7 @@ export const EventAdvancedTab = ({ isUserLoading, showToast, showBookerLayoutSelector, + customClassNames, }: EventAdvancedTabProps) => { const isPlatform = useIsPlatform(); const platformContext = useAtomsContext(); @@ -211,8 +258,18 @@ export const EventAdvancedTab = ({
{displayDestinationCalendarSelector && ( -
- +
+ ( @@ -222,15 +279,19 @@ export const EventAdvancedTab = ({ hidePlaceholder hideAdvancedText calendarsQueryData={calendarsQueryData} + customClassNames={customClassNames?.destinationCalendar} /> )} />

{t("select_which_cal")}

)} -
+
{displayDestinationCalendarSelector && ( -
+
) } + className={customClassNames?.addToCalendarEmailOrganizer?.emailSelect?.select} + containerClassName={customClassNames?.addToCalendarEmailOrganizer?.emailSelect?.container} onChange={(option) => formMethods.setValue("secondaryEmailId", option?.value, { shouldDirty: true }) } @@ -292,7 +355,13 @@ export const EventAdvancedTab = ({ )} options={verifiedSecondaryEmails} /> -

{t("display_email_as_organizer")}

+

+ {t("display_email_as_organizer")} +

)}
@@ -335,18 +404,23 @@ export const EventAdvancedTab = ({ requiresConfirmation={requiresConfirmation} requiresConfirmationWillBlockSlot={formMethods.getValues("requiresConfirmationWillBlockSlot")} onRequiresConfirmation={setRequiresConfirmation} + customClassNames={customClassNames?.requiresConfirmation} /> ( onChange(e)} /> @@ -356,9 +430,13 @@ export const EventAdvancedTab = ({ name="hideCalendarNotes" render={({ field: { value, onChange } }) => ( ( ( <> -
+
-
+
( onChange(e)} checked={value} /> @@ -433,7 +530,8 @@ export const EventAdvancedTab = ({
{t("redirect_url_warning")} @@ -489,13 +587,15 @@ export const EventAdvancedTab = ({ render={({ field: { value, onChange } }) => ( <> {t("seats")}} onChange={(e) => { onChange(Math.abs(Number(e.target.value))); }} data-testid="seats-per-time-slot" /> -
+
( onChange(e)} checked={value} @@ -560,7 +674,11 @@ export const EventAdvancedTab = ({ )} />
-
+
( @@ -569,6 +687,12 @@ export const EventAdvancedTab = ({ disabled={seatsLocked.disabled} onChange={(e) => onChange(e)} checked={value} + className={ + customClassNames?.seatsOptions?.showAvalableSeatCountCheckbox?.checkbox + } + descriptionClassName={ + customClassNames?.seatsOptions?.showAvalableSeatCountCheckbox?.description + } /> )} /> @@ -586,9 +710,13 @@ export const EventAdvancedTab = ({ name="lockTimeZoneToggleOnBookingPage" render={({ field: { value, onChange } }) => ( ( { const value = e ? eventTypeColorState : null; @@ -619,7 +749,7 @@ export const EventAdvancedTab = ({ }); setIsEventTypeColorChecked(e); }} - childrenClassName="lg:ml-0"> + childrenClassName={classNames("lg:ml-0", customClassNames?.eventTypeColors?.children)}>

{t("light_event_type_color")}

@@ -641,7 +771,11 @@ export const EventAdvancedTab = ({ /> {lightModeError ? (
- +
) : null}
@@ -666,7 +800,11 @@ export const EventAdvancedTab = ({ /> {darkModeError ? (
- +
) : null}
@@ -676,14 +814,18 @@ export const EventAdvancedTab = ({ /> {isRoundRobinEventType && ( ( onChange(e)} /> @@ -696,11 +838,15 @@ export const EventAdvancedTab = ({ render={({ field: { value, onChange } }) => ( <> onChange(e)} /> @@ -715,9 +861,13 @@ export const EventAdvancedTab = ({ render={({ field: { value, onChange } }) => ( <> )} + {team?.parentId && ( <> @@ -749,7 +901,13 @@ export const EventAdvancedTab = ({ defaultValue={!!formMethods.getValues("seatsPerTimeSlot")} render={({ field: { value, onChange } }) => ( <> - + )} /> @@ -763,6 +921,7 @@ export const EventAdvancedTab = ({ placeHolder={eventNamePlaceholder} isNameFieldSplit={isSplit} event={eventNameObject} + customClassNames={customClassNames?.customEventTypeModal} /> )}
diff --git a/packages/features/eventtypes/components/tabs/advanced/RequiresConfirmationController.tsx b/packages/features/eventtypes/components/tabs/advanced/RequiresConfirmationController.tsx index 644fac98ae..c59f00a4b9 100644 --- a/packages/features/eventtypes/components/tabs/advanced/RequiresConfirmationController.tsx +++ b/packages/features/eventtypes/components/tabs/advanced/RequiresConfirmationController.tsx @@ -7,13 +7,25 @@ import { Controller, useFormContext } from "react-hook-form"; import type z from "zod"; import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager"; -import type { EventTypeSetup } from "@calcom/features/eventtypes/lib/types"; +import type { EventTypeSetup, SettingsToggleClassNames } from "@calcom/features/eventtypes/lib/types"; import type { FormValues } from "@calcom/features/eventtypes/lib/types"; import { classNames } from "@calcom/lib"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; import { Input, SettingsToggle, RadioField, Select, CheckboxField } from "@calcom/ui"; +export type RequiresConfirmationCustomClassNames = SettingsToggleClassNames & { + radioGroupContainer?: string; + alwaysConfirmationRadio?: string; + conditionalConfirmationRadio?: { + container?: string; + timeInput?: string; + timeUnitSelect?: string; + checkbox?: string; + checkboxDescription?: string; + }; +}; + type RequiresConfirmationControllerProps = { metadata: z.infer; requiresConfirmation: boolean; @@ -21,6 +33,7 @@ type RequiresConfirmationControllerProps = { onRequiresConfirmation: Dispatch>; seatsEnabled: boolean; eventType: EventTypeSetup; + customClassNames?: RequiresConfirmationCustomClassNames; }; export default function RequiresConfirmationController({ @@ -29,6 +42,7 @@ export default function RequiresConfirmationController({ requiresConfirmation, onRequiresConfirmation, seatsEnabled, + customClassNames, }: RequiresConfirmationControllerProps) { const { t } = useLocale(); const [requiresConfirmationSetup, setRequiresConfirmationSetup] = useState( @@ -67,13 +81,15 @@ export default function RequiresConfirmationController({ control={formMethods.control} render={() => ( -
+
{(requiresConfirmationSetup === undefined || !requiresConfirmationLockedProps.disabled) && ( )} {(requiresConfirmationSetup !== undefined || @@ -131,7 +152,10 @@ export default function RequiresConfirmationController({ <>