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
This commit is contained in:
Somay Chauhan
2024-11-25 12:11:27 +05:30
committed by GitHub
parent 9e9dd6070d
commit eae74c568c
7 changed files with 591 additions and 113 deletions
@@ -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) {
@@ -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<CustomEventTypeModalFormProps> = (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<FormValues>();
const onSubmit: SubmitHandler<FormValues> = (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<CustomEventTypeModalFormProps> = (props) => {
if (isEmpty) {
setValue("");
}
handleSubmit(onSubmit)(e);
}}>
<TextField
@@ -60,72 +90,139 @@ const CustomEventTypeModalForm: FC<CustomEventTypeModalFormProps> = (props) => {
: validationResult;
},
})}
className="mb-0"
containerClassName={customClassNames?.eventNameInput?.container}
labelClassName={customClassNames?.eventNameInput?.label}
className={classNames("mb-0", customClassNames?.eventNameInput?.input)}
/>
<div className="pt-6 text-sm">
<div className="bg-subtle mb-6 rounded-md p-2">
<h1 className="text-emphasis mb-2 ml-1 font-medium">{t("available_variables")}</h1>
<div className="scroll-bar h-[216px] overflow-y-auto">
<div className="mb-2.5 flex font-normal">
<p className="text-subtle ml-1 mr-5 w-32">{`{Event type title}`}</p>
<p className="text-emphasis">{t("event_name_info")}</p>
<div
className={classNames(
"bg-subtle mb-6 rounded-md p-2",
customClassNames?.availableVariables?.container
)}>
<h1
className={classNames(
"text-emphasis mb-2 ml-1 font-medium",
customClassNames?.availableVariables?.header
)}>
{t("available_variables")}
</h1>
<div
className={classNames(
"scroll-bar h-[216px] overflow-y-auto",
customClassNames?.availableVariables?.scrollArea
)}>
<div
className={classNames(
"mb-2.5 flex font-normal",
customClassNames?.availableVariables?.variableItem?.container
)}>
<p
className={classNames(
"text-subtle ml-1 mr-5 w-32",
customClassNames?.availableVariables?.variableItem?.variableName
)}>
{`{Event type title}`}
</p>
<p
className={classNames(
"text-emphasis",
customClassNames?.availableVariables?.variableItem?.variableDescription
)}>
{t("event_name_info")}
</p>
</div>
<div className="mb-2.5 flex font-normal">
<p className="text-subtle ml-1 mr-5 w-32">{`{Event duration}`}</p>
<p className="text-emphasis">{t("event_duration_info")}</p>
</div>
<div className="mb-2.5 flex font-normal">
<p className="text-subtle ml-1 mr-5 w-32">{`{Organiser}`}</p>
<p className="text-emphasis">{t("your_full_name")}</p>
</div>
<div className="mb-2.5 flex font-normal">
<p className="text-subtle ml-1 mr-5 w-32">{`{Organiser first name}`}</p>
<p className="text-emphasis">{t("organizer_first_name")}</p>
</div>
<div className="mb-2.5 flex font-normal">
<p className="text-subtle ml-1 mr-5 w-32">{`{Scheduler}`}</p>
<p className="text-emphasis">{t("scheduler_full_name")}</p>
</div>
<div className="mb-2.5 flex font-normal">
<p className="text-subtle ml-1 mr-5 w-32">{`{Scheduler first name}`}</p>
<p className="text-emphasis">{t("scheduler_first_name")}</p>
</div>
{isNameFieldSplit && (
<div className="mb-2.5 flex font-normal">
<p className="text-subtle ml-1 mr-5 w-32">{`{Scheduler last name}`}</p>
<p className="text-emphasis">{t("scheduler_last_name")}</p>
</div>
)}
<div className="mb-2.5 flex font-normal">
<p className="text-subtle ml-1 mr-5 w-32">{`{Location}`}</p>
<p className="text-emphasis">{t("location_info")}</p>
<div
className={classNames(
"mb-2.5 flex font-normal",
customClassNames?.availableVariables?.variableItem?.container
)}>
<p
className={classNames(
"text-subtle ml-1 mr-5 w-32",
customClassNames?.availableVariables?.variableItem?.variableName
)}>
{`{Event duration}`}
</p>
<p
className={classNames(
"text-emphasis",
customClassNames?.availableVariables?.variableItem?.variableDescription
)}>
{t("event_duration_info")}
</p>
</div>
{/* Additional variable items here */}
{event.bookingFields && (
<p className="text-subtle mb-2 ml-1 font-medium">{t("booking_question_response_variables")}</p>
<p
className={classNames(
"text-subtle mb-2 ml-1 font-medium",
customClassNames?.availableVariables?.bookingFieldsHeader
)}>
{t("booking_question_response_variables")}
</p>
)}
{event.bookingFields &&
Object.keys(event.bookingFields).map((bookingfield, index) => (
<div key={index} className="mb-2.5 flex font-normal">
<p className="text-subtle ml-1 mr-5 w-32">{`{${bookingfield}}`}</p>
<p className="text-emphasis capitalize">
<div
key={index}
className={classNames(
"mb-2.5 flex font-normal",
customClassNames?.availableVariables?.bookingFieldItem?.container
)}>
<p
className={classNames(
"text-subtle ml-1 mr-5 w-32",
customClassNames?.availableVariables?.bookingFieldItem?.fieldName
)}>
{`{${bookingfield}}`}
</p>
<p
className={classNames(
"text-emphasis capitalize",
customClassNames?.availableVariables?.bookingFieldItem?.fieldValue
)}>
{event.bookingFields?.[bookingfield]?.toString()}
</p>
</div>
))}
</div>
</div>
<h1 className="mb-2 text-[14px] font-medium leading-4">{t("preview")}</h1>
<h1
className={classNames(
"mb-2 text-[14px] font-medium leading-4",
customClassNames?.previewSection?.header
)}>
{t("preview")}
</h1>
<div
className="flex h-[212px] w-full rounded-md border-y bg-cover bg-center dark:invert"
className={classNames(
"flex h-[212px] w-full rounded-md border-y bg-cover bg-center dark:invert",
customClassNames?.previewSection?.previewContainer
)}
style={{
backgroundImage: "url(/calendar-preview.svg)",
}}>
<div className="m-auto flex items-center justify-center self-stretch">
<div className="bg-subtle ml-11 mt-3 box-border h-[110px] w-[120px] flex-col items-start gap-1 rounded-md border border-solid border-black text-[12px] leading-3">
<p className="text-emphasis overflow-hidden text-ellipsis p-1.5 font-medium">{previewText}</p>
<p className="text-default ml-1.5 text-[10px] font-normal">8 - 10 AM</p>
<div
className={classNames(
"bg-subtle ml-11 mt-3 box-border h-[110px] w-[120px] flex-col items-start gap-1 rounded-md border border-solid border-black text-[12px] leading-3",
customClassNames?.previewSection?.previewEventBox?.container
)}>
<p
className={classNames(
"text-emphasis overflow-hidden text-ellipsis p-1.5 font-medium",
customClassNames?.previewSection?.previewEventBox?.eventName
)}>
{previewText}
</p>
<p
className={classNames(
"text-default ml-1.5 text-[10px] font-normal",
customClassNames?.previewSection?.previewEventBox?.eventTime
)}>
8 - 10 AM
</p>
</div>
</div>
</div>
@@ -141,12 +238,12 @@ interface CustomEventTypeModalProps {
setValue: (value: string) => void;
event: EventNameObjectType;
isNameFieldSplit: boolean;
customClassNames?: CustomEventTypeModalClassNames;
}
const CustomEventTypeModal: FC<CustomEventTypeModalProps> = (props) => {
const { t } = useLocale();
const { defaultValue, placeHolder, close, setValue, event, isNameFieldSplit } = props;
const { defaultValue, placeHolder, close, setValue, event, isNameFieldSplit, customClassNames } = props;
const methods = useForm<FormValues>({
defaultValues: {
@@ -169,11 +266,11 @@ const CustomEventTypeModal: FC<CustomEventTypeModalProps> = (props) => {
placeHolder={placeHolder}
defaultValue={defaultValue}
isNameFieldSplit={isNameFieldSplit}
customClassNames={customClassNames}
/>
</FormProvider>
<DialogFooter>
<DialogClose>{t("cancel")}</DialogClose>
<Button form="custom-event-name" type="submit" color="primary">
{t("create")}
</Button>
@@ -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 (
<div>
<Dialog open={dialogOpen} onOpenChange={(e) => setDialogOpen(e)}>
<DialogContent title={title} Icon="circle-alert">
<p className="text-default text-sm">
<DialogContent
title={title}
Icon="circle-alert"
className={customClassNames?.confirmationDialog?.container}>
<p
className={classNames("text-default text-sm", customClassNames?.confirmationDialog?.description)}>
<Trans i18nKey="disable_attendees_emails_description">
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.
</Trans>
</p>
<p className="text-default mb-1 mt-2 text-sm">{t("type_confirm_to_continue")}</p>
<p
className={classNames(
"text-default mb-1 mt-2 text-sm",
customClassNames?.confirmationDialog?.confirmInput?.label
)}>
{t("type_confirm_to_continue")}
</p>
<InputField
value={confirmText}
onChange={(e) => {
setConfirmText(e.target.value);
}}
/>
<DialogFooter>
<DialogClose />
<DialogFooter className={customClassNames?.confirmationDialog?.dialogFooter?.container}>
<DialogClose className={customClassNames?.confirmationDialog?.dialogFooter?.cancelButton} />
<Button
disabled={confirmText !== "confirm"}
onClick={(e) => {
onClick={() => {
onCheckedChange(true);
setDialogOpen(false);
}}>
}}
className={customClassNames?.confirmationDialog?.dialogFooter?.confirmButton}>
{t("disable_email")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<SettingsToggle
labelClassName="text-sm"
labelClassName={classNames("text-sm", customClassNames?.label)}
toggleSwitchAtTheEnd={true}
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
switchContainerClassName={classNames(
"border-subtle rounded-lg border py-6 px-4 sm:px-6",
customClassNames?.container
)}
descriptionClassName={customClassNames?.description}
title={title}
description={t("disable_all_emails_description")}
checked={!!checked}
onCheckedChange={() => {
checked ? onCheckedChange(!checked) : setDialogOpen(true);
}}
onCheckedChange={() => (checked ? onCheckedChange(!checked) : setDialogOpen(true))}
/>
</div>
);
@@ -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<typeof fieldSchema>;
export type EventAdvancedBaseProps = Pick<EventTypeSetupProps, "eventType" | "team"> & {
@@ -53,6 +98,7 @@ export type EventAdvancedBaseProps = Pick<EventTypeSetupProps, "eventType" | "te
>;
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 = ({
<div className="border-subtle space-y-6 rounded-lg border p-6">
<div className="flex flex-col space-y-4 lg:flex-row lg:space-x-4 lg:space-y-0">
{displayDestinationCalendarSelector && (
<div className="flex w-full flex-col">
<Label className="text-emphasis mb-0 font-medium">{t("add_to_calendar")}</Label>
<div
className={classNames(
"flex w-full flex-col",
customClassNames?.destinationCalendar?.container
)}>
<Label
className={classNames(
"text-emphasis mb-0 font-medium",
customClassNames?.destinationCalendar?.label
)}>
{t("add_to_calendar")}
</Label>
<Controller
name="destinationCalendar"
render={({ field: { onChange, value } }) => (
@@ -222,15 +279,19 @@ export const EventAdvancedTab = ({
hidePlaceholder
hideAdvancedText
calendarsQueryData={calendarsQueryData}
customClassNames={customClassNames?.destinationCalendar}
/>
)}
/>
<p className="text-subtle text-sm">{t("select_which_cal")}</p>
</div>
)}
<div className="w-full">
<div className={classNames("w-full", customClassNames?.eventName?.container)}>
<TextField
label={t("event_name_in_calendar")}
labelClassName={customClassNames?.eventName?.label}
addOnClassname={customClassNames?.eventName?.addOn}
className={customClassNames?.eventName?.input}
type="text"
{...eventNameLocked}
placeholder={eventNamePlaceholder}
@@ -250,7 +311,7 @@ export const EventAdvancedTab = ({
</div>
<div className="space-y-2">
{displayDestinationCalendarSelector && (
<div className="w-full">
<div className={classNames("w-full", customClassNames?.addToCalendarEmailOrganizer?.container)}>
<Switch
tooltip={t("if_enabled_email_address_as_organizer")}
label={
@@ -283,6 +344,8 @@ export const EventAdvancedTab = ({
</span>
)
}
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}
/>
<p className="text-subtle mt-2 text-sm">{t("display_email_as_organizer")}</p>
<p
className={classNames(
"text-subtle mt-2 text-sm",
customClassNames?.addToCalendarEmailOrganizer?.emailSelect?.displayEmailLabel
)}>
{t("display_email_as_organizer")}
</p>
</div>
)}
</div>
@@ -335,18 +404,23 @@ export const EventAdvancedTab = ({
requiresConfirmation={requiresConfirmation}
requiresConfirmationWillBlockSlot={formMethods.getValues("requiresConfirmationWillBlockSlot")}
onRequiresConfirmation={setRequiresConfirmation}
customClassNames={customClassNames?.requiresConfirmation}
/>
<Controller
name="requiresBookerEmailVerification"
render={({ field: { value, onChange } }) => (
<SettingsToggle
labelClassName="text-sm"
labelClassName={classNames("text-sm", customClassNames?.bookerEmailVerification?.label)}
toggleSwitchAtTheEnd={true}
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
switchContainerClassName={classNames(
"border-subtle rounded-lg border py-6 px-4 sm:px-6",
customClassNames?.bookerEmailVerification?.container
)}
title={t("requires_booker_email_verification")}
data-testid="requires-booker-email-verification"
{...requiresBookerEmailVerificationProps}
description={t("description_requires_booker_email_verification")}
descriptionClassName={customClassNames?.bookerEmailVerification?.description}
checked={value}
onCheckedChange={(e) => onChange(e)}
/>
@@ -356,9 +430,13 @@ export const EventAdvancedTab = ({
name="hideCalendarNotes"
render={({ field: { value, onChange } }) => (
<SettingsToggle
labelClassName="text-sm"
labelClassName={classNames("text-sm", customClassNames?.calendarNotes?.label)}
toggleSwitchAtTheEnd={true}
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
switchContainerClassName={classNames(
"border-subtle rounded-lg border py-6 px-4 sm:px-6",
customClassNames?.calendarNotes?.container
)}
descriptionClassName={customClassNames?.calendarNotes?.description}
data-testid="disable-notes"
title={t("disable_notes")}
{...hideCalendarNotesLocked}
@@ -372,9 +450,13 @@ export const EventAdvancedTab = ({
name="hideCalendarEventDetails"
render={({ field: { value, onChange } }) => (
<SettingsToggle
labelClassName="text-sm"
labelClassName={classNames("text-sm", customClassNames?.eventDetailsVisibility?.label)}
toggleSwitchAtTheEnd={true}
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
switchContainerClassName={classNames(
"border-subtle rounded-lg border py-6 px-4 sm:px-6",
customClassNames?.eventDetailsVisibility?.container
)}
descriptionClassName={customClassNames?.eventDetailsVisibility?.description}
title={t("hide_calendar_event_details")}
{...hideCalendarEventDetailsLocked}
description={t("description_hide_calendar_event_details")}
@@ -388,13 +470,15 @@ export const EventAdvancedTab = ({
render={({ field: { value, onChange } }) => (
<>
<SettingsToggle
labelClassName="text-sm"
labelClassName={classNames("text-sm", customClassNames?.bookingRedirect?.label)}
toggleSwitchAtTheEnd={true}
switchContainerClassName={classNames(
"border-subtle rounded-lg border py-6 px-4 sm:px-6",
redirectUrlVisible && "rounded-b-none"
redirectUrlVisible && "rounded-b-none",
customClassNames?.bookingRedirect?.container
)}
childrenClassName="lg:ml-0"
childrenClassName={classNames("lg:ml-0", customClassNames?.bookingRedirect?.children)}
descriptionClassName={customClassNames?.bookingRedirect?.description}
title={t("redirect_success_booking")}
data-testid="redirect-success-booking"
{...successRedirectUrlLocked}
@@ -404,10 +488,15 @@ export const EventAdvancedTab = ({
setRedirectUrlVisible(e);
onChange(e ? value : "");
}}>
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
<div
className={classNames(
"border-subtle rounded-b-lg border border-t-0 p-6",
customClassNames?.bookingRedirect?.redirectUrlInput?.container
)}>
<TextField
className="w-full"
className={classNames("w-full", customClassNames?.bookingRedirect?.redirectUrlInput?.input)}
label={t("redirect_success_booking")}
labelClassName={customClassNames?.bookingRedirect?.redirectUrlInput?.label}
labelSrOnly
disabled={successRedirectUrlLocked.disabled}
placeholder={t("external_redirect_url")}
@@ -417,13 +506,21 @@ export const EventAdvancedTab = ({
{...formMethods.register("successRedirectUrl")}
/>
<div className="mt-4">
<div
className={classNames(
"mt-4",
customClassNames?.bookingRedirect?.forwardParamsCheckbox?.container
)}>
<Controller
name="forwardParamsSuccessRedirect"
render={({ field: { value, onChange } }) => (
<CheckboxField
description={t("forward_params_redirect")}
disabled={successRedirectUrlLocked.disabled}
className={customClassNames?.bookingRedirect?.forwardParamsCheckbox?.checkbox}
descriptionClassName={
customClassNames?.bookingRedirect?.forwardParamsCheckbox?.description
}
onChange={(e) => onChange(e)}
checked={value}
/>
@@ -433,7 +530,8 @@ export const EventAdvancedTab = ({
<div
className={classNames(
"p-1 text-sm text-orange-600",
formMethods.getValues("successRedirectUrl") ? "block" : "hidden"
formMethods.getValues("successRedirectUrl") ? "block" : "hidden",
customClassNames?.bookingRedirect?.error
)}
data-testid="redirect-url-warning">
{t("redirect_url_warning")}
@@ -489,13 +587,15 @@ export const EventAdvancedTab = ({
render={({ field: { value, onChange } }) => (
<>
<SettingsToggle
labelClassName="text-sm"
labelClassName={classNames("text-sm", customClassNames?.seatsOptions?.label)}
toggleSwitchAtTheEnd={true}
switchContainerClassName={classNames(
"border-subtle rounded-lg border py-6 px-4 sm:px-6",
value && "rounded-b-none"
value && "rounded-b-none",
customClassNames?.seatsOptions?.container
)}
childrenClassName="lg:ml-0"
childrenClassName={classNames("lg:ml-0", customClassNames?.seatsOptions?.children)}
descriptionClassName={customClassNames?.seatsOptions?.description}
data-testid="offer-seats-toggle"
title={t("offer_seats")}
{...seatsLocked}
@@ -539,20 +639,34 @@ export const EventAdvancedTab = ({
disabled={seatsLocked.disabled}
defaultValue={value}
min={1}
containerClassName="max-w-80"
containerClassName={classNames(
"max-w-80",
customClassNames?.seatsOptions?.seatsInput.container
)}
addOnClassname={customClassNames?.seatsOptions?.seatsInput.addOn}
className={customClassNames?.seatsOptions?.seatsInput?.input}
labelClassName={customClassNames?.seatsOptions?.seatsInput?.label}
addOnSuffix={<>{t("seats")}</>}
onChange={(e) => {
onChange(Math.abs(Number(e.target.value)));
}}
data-testid="seats-per-time-slot"
/>
<div className="mt-4">
<div
className={classNames(
"mt-4",
customClassNames?.seatsOptions?.showAttendeesCheckbox?.container
)}>
<Controller
name="seatsShowAttendees"
render={({ field: { value, onChange } }) => (
<CheckboxField
data-testid="show-attendees"
description={t("show_attendees")}
className={customClassNames?.seatsOptions?.showAttendeesCheckbox?.checkbox}
descriptionClassName={
customClassNames?.seatsOptions?.showAttendeesCheckbox?.description
}
disabled={seatsLocked.disabled}
onChange={(e) => onChange(e)}
checked={value}
@@ -560,7 +674,11 @@ export const EventAdvancedTab = ({
)}
/>
</div>
<div className="mt-2">
<div
className={classNames(
"mt-2",
customClassNames?.seatsOptions?.showAvalableSeatCountCheckbox?.container
)}>
<Controller
name="seatsShowAvailabilityCount"
render={({ field: { value, onChange } }) => (
@@ -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 } }) => (
<SettingsToggle
labelClassName="text-sm"
labelClassName={classNames("text-sm", customClassNames?.timezoneLock?.label)}
descriptionClassName={customClassNames?.timezoneLock?.description}
toggleSwitchAtTheEnd={true}
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
switchContainerClassName={classNames(
"border-subtle rounded-lg border py-6 px-4 sm:px-6",
customClassNames?.timezoneLock?.container
)}
title={t("lock_timezone_toggle_on_booking_page")}
{...lockTimeZoneToggleOnBookingPageLocked}
description={t("description_lock_timezone_toggle_on_booking_page")}
@@ -602,15 +730,17 @@ export const EventAdvancedTab = ({
name="eventTypeColor"
render={() => (
<SettingsToggle
labelClassName="text-sm"
labelClassName={classNames("text-sm", customClassNames?.eventTypeColors?.label)}
toggleSwitchAtTheEnd={true}
switchContainerClassName={classNames(
"border-subtle rounded-lg border py-6 px-4 sm:px-6",
isEventTypeColorChecked && "rounded-b-none"
isEventTypeColorChecked && "rounded-b-none",
customClassNames?.eventTypeColors?.container
)}
title={t("event_type_color")}
{...eventTypeColorLocked}
description={t("event_type_color_description")}
descriptionClassName={customClassNames?.eventTypeColors?.description}
checked={isEventTypeColorChecked}
onCheckedChange={(e) => {
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)}>
<div className="border-subtle flex flex-col gap-6 rounded-b-lg border border-t-0 p-6">
<div>
<p className="text-default mb-2 block text-sm font-medium">{t("light_event_type_color")}</p>
@@ -641,7 +771,11 @@ export const EventAdvancedTab = ({
/>
{lightModeError ? (
<div className="mt-4">
<Alert severity="warning" message={t("event_type_color_light_theme_contrast_error")} />
<Alert
severity="warning"
className={customClassNames?.eventTypeColors?.warningText}
message={t("event_type_color_light_theme_contrast_error")}
/>
</div>
) : null}
</div>
@@ -666,7 +800,11 @@ export const EventAdvancedTab = ({
/>
{darkModeError ? (
<div className="mt-4">
<Alert severity="warning" message={t("event_type_color_dark_theme_contrast_error")} />
<Alert
severity="warning"
className={customClassNames?.eventTypeColors?.warningText}
message={t("event_type_color_dark_theme_contrast_error")}
/>
</div>
) : null}
</div>
@@ -676,14 +814,18 @@ export const EventAdvancedTab = ({
/>
{isRoundRobinEventType && (
<Controller
name="rescheduleWithSameRoundRobinHost"
name="roundRobinReschedule"
render={({ field: { value, onChange } }) => (
<SettingsToggle
labelClassName="text-sm"
labelClassName={classNames("text-sm", customClassNames?.roundRobinReschedule?.label)}
toggleSwitchAtTheEnd={true}
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
switchContainerClassName={classNames(
"border-subtle rounded-lg border py-6 px-4 sm:px-6",
customClassNames?.roundRobinReschedule?.container
)}
title={t("reschedule_with_same_round_robin_host_title")}
description={t("reschedule_with_same_round_robin_host_description")}
descriptionClassName={customClassNames?.roundRobinReschedule?.description}
checked={value}
onCheckedChange={(e) => onChange(e)}
/>
@@ -696,11 +838,15 @@ export const EventAdvancedTab = ({
render={({ field: { value, onChange } }) => (
<>
<SettingsToggle
labelClassName="text-sm"
labelClassName={classNames("text-sm", customClassNames?.emailNotifications?.label)}
toggleSwitchAtTheEnd={true}
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
switchContainerClassName={classNames(
"border-subtle rounded-lg border py-6 px-4 sm:px-6",
customClassNames?.emailNotifications?.container
)}
title={t("disable_attendees_confirmation_emails")}
description={t("disable_attendees_confirmation_emails_description")}
descriptionClassName={customClassNames?.emailNotifications?.description}
checked={value}
onCheckedChange={(e) => onChange(e)}
/>
@@ -715,9 +861,13 @@ export const EventAdvancedTab = ({
render={({ field: { value, onChange } }) => (
<>
<SettingsToggle
labelClassName="text-sm"
labelClassName={classNames("text-sm", customClassNames?.emailNotifications?.label)}
toggleSwitchAtTheEnd={true}
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
switchContainerClassName={classNames(
"border-subtle rounded-lg border py-6 px-4 sm:px-6",
customClassNames?.emailNotifications?.container
)}
descriptionClassName={customClassNames?.emailNotifications?.description}
title={t("disable_host_confirmation_emails")}
description={t("disable_host_confirmation_emails_description")}
checked={value}
@@ -727,6 +877,7 @@ export const EventAdvancedTab = ({
)}
/>
)}
{team?.parentId && (
<>
<Controller
@@ -738,6 +889,7 @@ export const EventAdvancedTab = ({
checked={value}
onCheckedChange={onChange}
recipient="attendees"
customClassNames={customClassNames?.emailNotifications}
t={t}
/>
</>
@@ -749,7 +901,13 @@ export const EventAdvancedTab = ({
defaultValue={!!formMethods.getValues("seatsPerTimeSlot")}
render={({ field: { value, onChange } }) => (
<>
<DisableAllEmailsSetting checked={value} onCheckedChange={onChange} recipient="hosts" t={t} />
<DisableAllEmailsSetting
checked={value}
onCheckedChange={onChange}
recipient="hosts"
customClassNames={customClassNames?.emailNotifications}
t={t}
/>
</>
)}
/>
@@ -763,6 +921,7 @@ export const EventAdvancedTab = ({
placeHolder={eventNamePlaceholder}
isNameFieldSplit={isSplit}
event={eventNameObject}
customClassNames={customClassNames?.customEventTypeModal}
/>
)}
</div>
@@ -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<typeof EventTypeMetaDataSchema>;
requiresConfirmation: boolean;
@@ -21,6 +33,7 @@ type RequiresConfirmationControllerProps = {
onRequiresConfirmation: Dispatch<SetStateAction<boolean>>;
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={() => (
<SettingsToggle
labelClassName="text-sm"
labelClassName={classNames("text-sm", customClassNames?.label)}
toggleSwitchAtTheEnd={true}
switchContainerClassName={classNames(
"border-subtle rounded-lg border py-6 px-4 sm:px-6",
requiresConfirmation && "rounded-b-none"
requiresConfirmation && "rounded-b-none",
customClassNames?.container
)}
childrenClassName="lg:ml-0"
childrenClassName={classNames("lg:ml-0", customClassNames?.children)}
descriptionClassName={customClassNames?.description}
title={t("requires_confirmation")}
data-testid="requires-confirmation"
disabled={seatsEnabled || requiresConfirmationLockedProps.disabled}
@@ -116,7 +132,11 @@ export default function RequiresConfirmationController({
);
}
}}>
<div className="flex flex-col flex-wrap justify-start gap-y-2">
<div
className={classNames(
"flex flex-col flex-wrap justify-start gap-y-2",
customClassNames?.radioGroupContainer
)}>
{(requiresConfirmationSetup === undefined ||
!requiresConfirmationLockedProps.disabled) && (
<RadioField
@@ -124,6 +144,7 @@ export default function RequiresConfirmationController({
disabled={requiresConfirmationLockedProps.disabled}
id="always"
value="always"
className={customClassNames?.alwaysConfirmationRadio}
/>
)}
{(requiresConfirmationSetup !== undefined ||
@@ -131,7 +152,10 @@ export default function RequiresConfirmationController({
<>
<RadioField
disabled={requiresConfirmationLockedProps.disabled}
className="items-center"
className={classNames(
"items-center",
customClassNames?.conditionalConfirmationRadio?.container
)}
label={
<>
<Trans
@@ -158,7 +182,10 @@ export default function RequiresConfirmationController({
{ shouldDirty: true }
);
}}
className="border-default !m-0 block w-16 rounded-r-none border-r-0 text-sm [appearance:textfield] focus:z-10 focus:border-r"
className={classNames(
"border-default !m-0 block w-16 rounded-r-none border-r-0 text-sm [appearance:textfield] focus:z-10 focus:border-r",
customClassNames?.conditionalConfirmationRadio?.timeInput
)}
defaultValue={metadata?.requiresConfirmationThreshold?.time || 30}
/>
<label
@@ -170,6 +197,9 @@ export default function RequiresConfirmationController({
options={options}
isSearchable={false}
isDisabled={requiresConfirmationLockedProps.disabled}
className={
customClassNames?.conditionalConfirmationRadio?.timeUnitSelect
}
innerClassNames={{ control: "rounded-l-none bg-subtle" }}
onChange={(opt) => {
setRequiresConfirmationSetup({
@@ -200,6 +230,10 @@ export default function RequiresConfirmationController({
checked={requiresConfirmationWillBlockSlot}
descriptionAsLabel
description={t("requires_confirmation_will_block_slot_description")}
className={customClassNames?.conditionalConfirmationRadio?.checkbox}
descriptionClassName={
customClassNames?.conditionalConfirmationRadio?.checkboxDescription
}
onChange={(e) => {
// We set should dirty to properly detect when we can submit the form
formMethods.setValue("requiresConfirmationWillBlockSlot", e.target.checked, {
@@ -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 { EventAdvancedTabCustomClassNames } from "@calcom/features/eventtypes/components/tabs/advanced/EventAdvancedTab";
import type { EventTeamAssignmentTabCustomClassNames } from "@calcom/features/eventtypes/components/tabs/assignment/EventTeamAssignmentTab";
import type { EventAvailabilityTabCustomClassNames } from "@calcom/features/eventtypes/components/tabs/availability/EventAvailabilityTab";
import type { EventRecurringTabCustomClassNames } from "@calcom/features/eventtypes/components/tabs/recurring/RecurringEventController";
@@ -35,6 +36,7 @@ export type PlatformTabs = keyof Omit<TabMap, "workflows" | "webhooks" | "instan
export type EventTypeCustomClassNames = {
atomsWrapper?: string;
eventAdvancedTab?: EventAdvancedTabCustomClassNames;
eventAssignmentTab?: EventTeamAssignmentTabCustomClassNames;
eventRecurringTab?: EventRecurringTabCustomClassNames;
eventAvailabilityTab?: EventAvailabilityTabCustomClassNames;
@@ -184,6 +186,7 @@ const EventType = ({
user={user?.data}
isUserLoading={isUserLoading}
showToast={showToast}
customClassNames={customClassNames?.eventAdvancedTab}
/>
) : (
<></>
@@ -97,6 +97,155 @@ export default function Bookings(props: { calUsername: string; calEmail: string
<EventTypeSettings
customClassNames={{
atomsWrapper: "!w-[50vw] !m-auto",
eventAdvancedTab: {
destinationCalendar: {
container: "bg-purple-50 border border-purple-200 shadow-md",
select: "border border-purple-300 shadow-sm",
innerClassNames: {
input: "",
option: "",
control: "",
singleValue: "",
valueContainer: "",
multiValue: "",
menu: "",
menuList: "",
},
label: "",
},
eventName: {
container: "bg-purple-50 border border-purple-200 shadow-sm",
input: "border border-purple-300 shadow-inner",
label: "",
addOn: "",
},
addToCalendarEmailOrganizer: {
container: "bg-purple-50 border border-purple-200 shadow-inner",
label: "",
description: "",
children: "",
emailSelect: {
container: "",
select: "",
displayEmailLabel: "",
},
},
requiresConfirmation: {
container: "bg-purple-50 border border-purple-200 shadow-md",
label: "",
description: "",
children: "",
radioGroupContainer: "",
alwaysConfirmationRadio: "",
conditionalConfirmationRadio: {
container: "",
timeInput: "",
timeUnitSelect: "",
checkbox: "",
checkboxDescription: "",
},
},
bookerEmailVerification: {
container: "bg-purple-50 border border-purple-200 shadow-md",
label: "",
description: "",
children: "",
},
calendarNotes: {
container: "bg-purple-50 border border-purple-200 shadow-md",
label: "",
description: "",
children: "",
},
eventDetailsVisibility: {
container: "bg-purple-50 border border-purple-200 shadow-md",
label: "",
description: "",
children: "",
},
bookingRedirect: {
container: "bg-purple-50 border border-purple-200 shadow-md",
label: "",
description: "",
children: "",
redirectUrlInput: {
container: "bg-purple-50 border border-purple-200 shadow-sm",
label: "",
input: "",
addOn: "",
},
forwardParamsCheckbox: {
checkbox: "",
description: "",
container: "bg-purple-50 border border-purple-300 shadow-sm",
},
error: "bg-purple-50 text-purple-700 border border-purple-300",
},
seatsOptions: {
container: "bg-purple-50 border border-purple-200 shadow-md",
label: "",
description: "",
children: "",
seatsInput: {
container: "bg-purple-50 border border-purple-200 shadow-sm",
label: "",
input: "",
addOn: "",
},
showAttendeesCheckbox: {
checkbox: "",
description: "",
container: "",
},
showAvalableSeatCountCheckbox: {
checkbox: "",
description: "",
container: "",
},
},
timezoneLock: {
container: "bg-purple-50 border border-purple-200 shadow-md",
label: "",
description: "",
children: "",
},
eventTypeColors: {
container: "bg-purple-50 border border-purple-200 shadow-md",
label: "",
description: "",
children: "",
warningText: "",
},
roundRobinReschedule: {
container: "bg-purple-50 border border-purple-200 shadow-md",
label: "",
description: "",
children: "",
},
emailNotifications: {
container: "bg-purple-50 border border-purple-200 shadow-md",
label: "",
description: "",
children: "",
confirmationDialog: {
container: "bg-purple-50 border border-purple-300 shadow-lg",
dialogTitle: "",
description: "",
confirmInput: {
container: "",
label: "",
input: "",
addOn: "",
},
dialogFooter: {
container: "bg-purple-50",
confirmButton: "",
cancelButton: "",
},
},
},
},
eventAssignmentTab: {
assignmentType: {
container: "border border-purple-200 bg-white rounded-lg p-6",