refactor: event type settings (#11539)

Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
pull/11562/head
Udit Takkar 2023-09-28 17:29:06 +05:30 committed by GitHub
parent ab17cb216f
commit ef45cbfb3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 861 additions and 686 deletions

View File

@ -34,7 +34,7 @@ import {
TextField,
Tooltip,
} from "@calcom/ui";
import { Copy, Edit } from "@calcom/ui/components/icon";
import { Copy, Edit, Info } from "@calcom/ui/components/icon";
import { IS_VISUAL_REGRESSION_TESTING } from "@calcom/web/constants";
import RequiresConfirmationController from "./RequiresConfirmationController";
@ -124,16 +124,17 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
const setEventName = (value: string) => formMethods.setValue("eventName", value);
return (
<div className="flex flex-col space-y-8">
<div className="flex flex-col space-y-4">
{/**
* Only display calendar selector if user has connected calendars AND if it's not
* a team event. Since we don't have logic to handle each attendee calendar (for now).
* This will fallback to each user selected destination calendar.
*/}
<div className="border-subtle space-y-6 rounded-md border p-6">
{!!connectedCalendarsQuery.data?.connectedCalendars.length && !team && (
<div className="flex flex-col">
<div className="flex justify-between">
<Label>{t("add_to_calendar")}</Label>
<Label className="font-medium">{t("add_to_calendar")}</Label>
<Link
href="/apps/categories/calendar"
target="_blank"
@ -156,7 +157,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
)}
/>
</div>
<p className="text-default text-sm">{t("select_which_cal")}</p>
<p className="text-subtle text-sm">{t("select_which_cal")}</p>
</div>
)}
<div className="w-full">
@ -179,11 +180,11 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
}
/>
</div>
<hr className="border-subtle [&:has(+div:empty)]:hidden" />
<div>
<BookerLayoutSelector fallbackToUserSettings isDark={selectedThemeIsDark} />
</div>
<hr className="border-subtle" />
<BookerLayoutSelector fallbackToUserSettings isDark={selectedThemeIsDark} />
<div className="border-subtle space-y-6 rounded-md border p-6">
<FormBuilder
title={t("booking_questions_title")}
description={t("booking_questions_description")}
@ -196,7 +197,8 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
},
}}
/>
<hr className="border-subtle" />
</div>
<RequiresConfirmationController
eventType={eventType}
seatsEnabled={seatsEnabled}
@ -204,13 +206,15 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
requiresConfirmation={requiresConfirmation}
onRequiresConfirmation={setRequiresConfirmation}
/>
<hr className="border-subtle" />
<Controller
name="requiresBookerEmailVerification"
control={formMethods.control}
defaultValue={eventType.requiresBookerEmailVerification}
render={({ field: { value, onChange } }) => (
<SettingsToggle
toggleSwitchAtTheEnd={true}
switchContainerClassName="border-subtle rounded-md border py-6 px-4 sm:px-6"
title={t("requires_booker_email_verification")}
{...shouldLockDisableProps("requiresBookerEmailVerification")}
description={t("description_requires_booker_email_verification")}
@ -219,13 +223,15 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
/>
)}
/>
<hr className="border-subtle" />
<Controller
name="hideCalendarNotes"
control={formMethods.control}
defaultValue={eventType.hideCalendarNotes}
render={({ field: { value, onChange } }) => (
<SettingsToggle
toggleSwitchAtTheEnd={true}
switchContainerClassName="border-subtle rounded-md border py-6 px-4 sm:px-6"
title={t("disable_notes")}
{...shouldLockDisableProps("hideCalendarNotes")}
description={t("disable_notes_description")}
@ -234,13 +240,19 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
/>
)}
/>
<hr className="border-subtle" />
<Controller
name="successRedirectUrl"
control={formMethods.control}
render={({ field: { value, onChange } }) => (
<>
<SettingsToggle
toggleSwitchAtTheEnd={true}
switchContainerClassName={classNames(
"border-subtle rounded-md border py-6 px-4 sm:px-6",
redirectUrlVisible && "rounded-b-none"
)}
childrenClassName="lg:ml-0"
title={t("redirect_success_booking")}
{...successRedirectUrlLocked}
description={t("redirect_url_description")}
@ -249,8 +261,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
setRedirectUrlVisible(e);
onChange(e ? value : "");
}}>
{/* Textfield has some margin by default we remove that so we can keep consistent alignment */}
<div className="lg:-mb-2 lg:-ml-2">
<div className="border-subtle rounded-b-md border border-t-0 p-6">
<TextField
className="w-full"
label={t("redirect_success_booking")}
@ -274,10 +285,24 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
</>
)}
/>
<hr className="border-subtle" />
<SettingsToggle
toggleSwitchAtTheEnd={true}
switchContainerClassName={classNames(
"border-subtle rounded-md border py-6 px-4 sm:px-6",
hashedLinkVisible && "rounded-b-none"
)}
childrenClassName="lg:ml-0"
data-testid="hashedLinkCheck"
title={t("private_link")}
Badge={
<a
target="_blank"
rel="noreferrer"
href="https://cal.com/docs/core-features/event-types/single-use-private-links">
<Info className="mb-2 ml-1.5 h-4 w-4 cursor-pointer" />
</a>
}
{...shouldLockDisableProps("hashedLinkCheck")}
description={t("private_link_description", { appName: APP_NAME })}
checked={hashedLinkVisible}
@ -285,8 +310,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
formMethods.setValue("hashedLink", e ? hashedUrl : undefined);
setHashedLinkVisible(e);
}}>
{/* Textfield has some margin by default we remove that so we can keep consitant aligment */}
<div className="lg:-ml-2">
<div className="border-subtle rounded-b-md border border-t-0 p-6">
{!IS_VISUAL_REGRESSION_TESTING && (
<TextField
disabled
@ -321,7 +345,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
)}
</div>
</SettingsToggle>
<hr className="border-subtle" />
<Controller
name="seatsPerTimeSlotEnabled"
control={formMethods.control}
@ -329,6 +353,12 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
render={({ field: { value, onChange } }) => (
<>
<SettingsToggle
toggleSwitchAtTheEnd={true}
switchContainerClassName={classNames(
"border-subtle rounded-md border py-6 px-4 sm:px-6",
value && "rounded-b-none"
)}
childrenClassName="lg:ml-0"
data-testid="offer-seats-toggle"
title={t("offer_seats")}
{...seatsLocked}
@ -349,6 +379,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
}
onChange(e);
}}>
<div className="border-subtle rounded-b-md border border-t-0 p-6">
<Controller
name="seatsPerTimeSlot"
control={formMethods.control}
@ -381,13 +412,16 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
<CheckboxField
description={t("show_available_seats_count")}
disabled={seatsLocked.disabled}
onChange={(e) => formMethods.setValue("seatsShowAvailabilityCount", e.target.checked)}
onChange={(e) =>
formMethods.setValue("seatsShowAvailabilityCount", e.target.checked)
}
defaultChecked={!!eventType.seatsShowAvailabilityCount}
/>
</div>
</div>
)}
/>
</div>
</SettingsToggle>
{noShowFeeEnabled && <Alert severity="warning" title={t("seats_and_no_show_fee_error")} />}
</>
@ -395,13 +429,14 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
/>
{allowDisablingAttendeeConfirmationEmails(workflows) && (
<>
<hr className="border-subtle" />
<Controller
name="metadata.disableStandardEmails.confirmation.attendee"
control={formMethods.control}
render={({ field: { value, onChange } }) => (
<>
<SettingsToggle
toggleSwitchAtTheEnd={true}
switchContainerClassName="border-subtle rounded-md border py-6 px-4 sm:px-6"
title={t("disable_attendees_confirmation_emails")}
description={t("disable_attendees_confirmation_emails_description")}
checked={value || false}
@ -417,7 +452,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
)}
{allowDisablingHostConfirmationEmails(workflows) && (
<>
<hr className="border-subtle" />
<Controller
name="metadata.disableStandardEmails.confirmation.host"
control={formMethods.control}
@ -425,6 +459,8 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
render={({ field: { value, onChange } }) => (
<>
<SettingsToggle
toggleSwitchAtTheEnd={true}
switchContainerClassName="border-subtle rounded-md border py-6 px-4 sm:px-6"
title={t("disable_host_confirmation_emails")}
description={t("disable_host_confirmation_emails_description")}
checked={value || false}

View File

@ -158,7 +158,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
</div>
</div>
{!shouldLockDisableProps("apps").disabled && (
<div className="bg-muted rounded-md p-8">
<div className="bg-muted mt-6 rounded-md p-8">
{!isLoading && notInstalledApps?.length ? (
<>
<h2 className="text-emphasis mb-2 text-xl font-semibold leading-5 tracking-[0.01em]">
@ -166,7 +166,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
</h2>
<p className="text-default mb-6 text-sm font-normal">
<Trans i18nKey="available_apps_desc">
You have no apps installed. View popular apps below and explore more in our &nbsp;
View popular apps below and explore more in our &nbsp;
<Link className="cursor-pointer underline" href="/apps">
App Store
</Link>

View File

@ -98,7 +98,8 @@ const EventTypeScheduleDetails = memo(
schedule?.schedule.filter((item) => item.days.includes((dayNum + 1) % 7)) || [];
return (
<div className="border-default space-y-4 rounded border px-6 pb-4">
<div>
<div className="border-subtle space-y-4 border-x p-6">
<ol className="table border-collapse text-sm">
{weekdayNames(i18n.language, 1, "long").map((day, index) => {
const isAvailable = !!filterDays(index).length;
@ -132,8 +133,8 @@ const EventTypeScheduleDetails = memo(
);
})}
</ol>
<hr className="border-subtle" />
<div className="flex flex-col justify-center gap-2 sm:flex-row sm:justify-between">
</div>
<div className="bg-muted border-subtle flex flex-col justify-center gap-2 rounded-b-md border p-6 sm:flex-row sm:justify-between">
<span className="text-default flex items-center justify-center text-sm sm:justify-start">
<Globe className="h-3.5 w-3.5 ltr:mr-2 rtl:ml-2" />
{schedule?.timeZone || <SkeletonText className="block h-5 w-32" />}
@ -234,8 +235,8 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
}, [availabilityValue, setValue]);
return (
<div className="space-y-4">
<div>
<div className="border-subtle rounded-t-md border p-6">
<label htmlFor="availability" className="text-default mb-2 block text-sm font-medium leading-none">
{t("availability")}
{shouldLockIndicator("availability")}

View File

@ -17,7 +17,7 @@ import { ascendingLimitKeys, intervalLimitKeyToUnit } from "@calcom/lib/interval
import type { PeriodType } from "@calcom/prisma/enums";
import type { IntervalLimit } from "@calcom/types/Calendar";
import { Button, DateRangePicker, InputField, Label, Select, SettingsToggle, TextField } from "@calcom/ui";
import { Plus, Trash } from "@calcom/ui/components/icon";
import { Plus, Trash2 } from "@calcom/ui/components/icon";
const MinimumBookingNoticeInput = React.forwardRef<
HTMLInputElement,
@ -83,14 +83,14 @@ const MinimumBookingNoticeInput = React.forwardRef<
type="number"
placeholder="0"
min={0}
className="mb-0 h-[38px] rounded-[4px] ltr:mr-2 rtl:ml-2"
className="mb-0 h-9 rounded-[4px] ltr:mr-2 rtl:ml-2"
/>
<input type="hidden" ref={ref} {...passThroughProps} />
</div>
<Select
isSearchable={false}
isDisabled={passThroughProps.disabled}
className="mb-0 ml-2 h-[38px] w-full capitalize md:min-w-[150px] md:max-w-[200px]"
className="mb-0 ml-2 h-9 w-full capitalize md:min-w-[150px] md:max-w-[200px]"
defaultValue={durationTypeOptions.find(
(option) => option.value === minimumBookingNoticeDisplayValues.type
)}
@ -170,8 +170,8 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
const offsetAdjustedTime = new Date(offsetOriginalTime.getTime() + offsetStartValue * 60 * 1000);
return (
<div className="space-y-8">
<div className="space-y-4 lg:space-y-8">
<div>
<div className="border-subtle space-y-6 rounded-md border p-6">
<div className="flex flex-col space-y-4 lg:flex-row lg:space-x-4 lg:space-y-0">
<div className="w-full">
<Label htmlFor="beforeBufferTime">
@ -295,16 +295,18 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
</div>
</div>
</div>
<hr className="border-subtle" />
<Controller
name="bookingLimits"
control={formMethods.control}
render={({ field: { value } }) => (
render={({ field: { value } }) => {
const isChecked = Object.keys(value ?? {}).length > 0;
return (
<SettingsToggle
toggleSwitchAtTheEnd={true}
title={t("limit_booking_frequency")}
{...bookingLimitsLocked}
description={t("limit_booking_frequency_description")}
checked={Object.keys(value ?? {}).length > 0}
checked={isChecked}
onCheckedChange={(active) => {
if (active) {
formMethods.setValue("bookingLimits", {
@ -313,26 +315,41 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
} else {
formMethods.setValue("bookingLimits", {});
}
}}>
}}
switchContainerClassName={classNames(
"border-subtle mt-6 rounded-md border py-6 px-4 sm:px-6",
isChecked && "rounded-b-none"
)}
childrenClassName="lg:ml-0">
<div className="border-subtle rounded-b-md border border-t-0 p-6">
<IntervalLimitsManager
disabled={bookingLimitsLocked.disabled}
propertyName="bookingLimits"
defaultLimit={1}
step={1}
/>
</div>
</SettingsToggle>
)}
);
}}
/>
<hr className="border-subtle" />
<Controller
name="durationLimits"
control={formMethods.control}
render={({ field: { value } }) => (
render={({ field: { value } }) => {
const isChecked = Object.keys(value ?? {}).length > 0;
return (
<SettingsToggle
toggleSwitchAtTheEnd={true}
switchContainerClassName={classNames(
"border-subtle mt-6 rounded-md border py-6 px-4 sm:px-6",
isChecked && "rounded-b-none"
)}
childrenClassName="lg:ml-0"
title={t("limit_total_booking_duration")}
description={t("limit_total_booking_duration_description")}
{...durationLimitsLocked}
checked={Object.keys(value ?? {}).length > 0}
checked={isChecked}
onCheckedChange={(active) => {
if (active) {
formMethods.setValue("durationLimits", {
@ -342,6 +359,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
formMethods.setValue("durationLimits", {});
}
}}>
<div className="border-subtle rounded-b-md border border-t-0 p-6">
<IntervalLimitsManager
propertyName="durationLimits"
defaultLimit={60}
@ -349,20 +367,31 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
step={15}
textFieldSuffix={t("minutes")}
/>
</div>
</SettingsToggle>
)}
);
}}
/>
<hr className="border-subtle" />
<Controller
name="periodType"
control={formMethods.control}
render={({ field: { value } }) => (
render={({ field: { value } }) => {
const isChecked = value && value !== "UNLIMITED";
return (
<SettingsToggle
toggleSwitchAtTheEnd={true}
switchContainerClassName={classNames(
"border-subtle mt-6 rounded-md border py-6 px-4 sm:px-6",
isChecked && "rounded-b-none"
)}
childrenClassName="lg:ml-0"
title={t("limit_future_bookings")}
description={t("limit_future_bookings_description")}
{...periodTypeLocked}
checked={value && value !== "UNLIMITED"}
checked={isChecked}
onCheckedChange={(bool) => formMethods.setValue("periodType", bool ? "ROLLING" : "UNLIMITED")}>
<div className="border-subtle rounded-b-md border border-t-0 p-6">
<RadioGroup.Root
defaultValue={watchPeriodType}
value={watchPeriodType}
@ -443,11 +472,18 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
);
})}
</RadioGroup.Root>
</div>
</SettingsToggle>
)}
);
}}
/>
<hr className="border-subtle" />
<SettingsToggle
toggleSwitchAtTheEnd={true}
switchContainerClassName={classNames(
"border-subtle mt-6 rounded-md border py-6 px-4 sm:px-6",
offsetToggle && "rounded-b-none"
)}
childrenClassName="lg:ml-0"
title={t("offset_toggle")}
description={t("offset_toggle_description")}
{...offsetStartLockedProps}
@ -458,6 +494,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
formMethods.setValue("offsetStart", 0);
}
}}>
<div className="border-subtle rounded-b-md border border-t-0 p-6">
<TextField
required
type="number"
@ -470,6 +507,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
adjustedTime: offsetAdjustedTime.toLocaleTimeString(i18n.language, { timeStyle: "short" }),
})}
/>
</div>
</SettingsToggle>
</div>
);
@ -509,19 +547,19 @@ const IntervalLimitItem = ({
onIntervalSelect,
}: IntervalLimitItemProps) => {
return (
<div className="mb-2 flex items-center space-x-2 text-sm rtl:space-x-reverse" key={limitKey}>
<div className="mb-4 flex max-h-9 items-center space-x-2 text-sm rtl:space-x-reverse" key={limitKey}>
<TextField
required
type="number"
containerClassName={textFieldSuffix ? "w-44 -mb-1" : "w-16 mb-0"}
className="mb-0 !h-auto"
className="mb-0"
placeholder={`${value}`}
disabled={disabled}
min={step}
step={step}
defaultValue={value}
addOnSuffix={textFieldSuffix}
onChange={(e) => onLimitChange(limitKey, parseInt(e.target.value))}
onChange={(e) => onLimitChange(limitKey, parseInt(e.target.value || "0", 10))}
/>
<Select
options={selectOptions}
@ -529,9 +567,16 @@ const IntervalLimitItem = ({
isDisabled={disabled}
defaultValue={INTERVAL_LIMIT_OPTIONS.find((option) => option.value === limitKey)}
onChange={onIntervalSelect}
className="w-36"
/>
{hasDeleteButton && !disabled && (
<Button variant="icon" StartIcon={Trash} color="destructive" onClick={() => onDelete(limitKey)} />
<Button
variant="icon"
StartIcon={Trash2}
color="destructive"
className="border-none"
onClick={() => onDelete(limitKey)}
/>
)}
</div>
);

View File

@ -387,7 +387,8 @@ export const EventSetupTab = (
return (
<div>
<div className="space-y-8">
<div className="space-y-4">
<div className="border-subtle space-y-6 rounded-md border p-6">
<TextField
required
label={t("title")}
@ -425,8 +426,10 @@ export const EventSetupTab = (
setValueAs: (v) => slugify(v),
})}
/>
</div>
<div className="border-subtle rounded-md border p-6">
{multipleDuration ? (
<div className="space-y-4">
<div className="space-y-6">
<div>
<Skeleton as={Label} loadingClassName="w-16">
{t("available_durations")}
@ -518,6 +521,9 @@ export const EventSetupTab = (
/>
</div>
)}
</div>
<div className="border-subtle rounded-md border p-6">
<div>
<Skeleton as={Label} loadingClassName="w-16">
{t("location")}
@ -560,5 +566,6 @@ export const EventSetupTab = (
teamId={eventType.team?.id}
/>
</div>
</div>
);
};

View File

@ -1,5 +1,7 @@
import type { Webhook } from "@prisma/client";
import { Webhook as TbWebhook } from "lucide-react";
import { Trans } from "next-i18next";
import Link from "next/link";
import type { EventTypeSetupProps } from "pages/event-types/[type]";
import { useState } from "react";
@ -8,6 +10,7 @@ import { WebhookForm } from "@calcom/features/webhooks/components";
import type { WebhookFormSubmitData } from "@calcom/features/webhooks/components/WebhookForm";
import WebhookListItem from "@calcom/features/webhooks/components/WebhookListItem";
import { subscriberUrlReserved } from "@calcom/features/webhooks/lib/subscriberUrlReserved";
import { APP_NAME } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Alert, Button, Dialog, DialogContent, EmptyScreen, showToast } from "@calcom/ui";
@ -115,7 +118,13 @@ export const EventWebhooksTab = ({ eventType }: Pick<EventTypeSetupProps, "event
)}
{webhooks.length ? (
<>
<div className="mb-2 rounded-md border">
<div className="border-subtle mb-2 rounded-md border p-8">
<div className="text-default text-sm font-semibold">{t("webhooks")}</div>
<p className="text-subtle max-w-[280px] break-words text-sm sm:max-w-[500px]">
{t("add_webhook_description", { appName: APP_NAME })}
</p>
<div className="border-subtle mt-8 rounded-md border">
{webhooks.map((webhook, index) => {
return (
<WebhookListItem
@ -131,7 +140,18 @@ export const EventWebhooksTab = ({ eventType }: Pick<EventTypeSetupProps, "event
);
})}
</div>
<NewWebhookButton />
<p className="text-default mt-8 text-sm font-normal">
<Trans i18nKey="edit_or_manage_webhooks">
If you wish to edit or manage your web hooks, please head over to &nbsp;
<Link
className="cursor-pointer font-semibold underline"
href="/settings/developer/webhooks">
webhooks settings
</Link>
</Trans>
</p>
</div>
</>
) : (
<EmptyScreen

View File

@ -3,6 +3,7 @@ import { useState } from "react";
import { useFormContext } from "react-hook-form";
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Frequency } from "@calcom/prisma/zod-utils";
import type { RecurringEvent } from "@calcom/types/Calendar";
@ -47,6 +48,12 @@ export default function RecurringEventController({
) : (
<>
<SettingsToggle
toggleSwitchAtTheEnd={true}
switchContainerClassName={classNames(
"border-subtle rounded-md border py-6 px-4 sm:px-6",
recurringEventState !== null && "rounded-b-none"
)}
childrenClassName="lg:ml-0"
title={t("recurring_event")}
{...recurringLocked}
description={t("recurring_event_description")}
@ -66,6 +73,7 @@ export default function RecurringEventController({
setRecurringEventState(newVal);
}
}}>
<div className="border-subtle rounded-b-md border border-t-0 p-6">
{recurringEventState && (
<div data-testid="recurring-event-collapsible" className="text-sm">
<div className="flex items-center">
@ -128,6 +136,7 @@ export default function RecurringEventController({
</div>
</div>
)}
</div>
</SettingsToggle>
</>
)}

View File

@ -67,6 +67,12 @@ export default function RequiresConfirmationController({
control={formMethods.control}
render={() => (
<SettingsToggle
toggleSwitchAtTheEnd={true}
switchContainerClassName={classNames(
"border-subtle rounded-md border py-6 px-4 sm:px-6",
requiresConfirmation && "rounded-b-none"
)}
childrenClassName="lg:ml-0"
title={t("requires_confirmation")}
disabled={seatsEnabled || requiresConfirmationLockedProps.disabled}
tooltip={seatsEnabled ? t("seat_options_doesnt_support_confirmation") : undefined}
@ -77,6 +83,7 @@ export default function RequiresConfirmationController({
formMethods.setValue("requiresConfirmation", val);
onRequiresConfirmation(val);
}}>
<div className="border-subtle rounded-b-md border border-t-0 p-6">
<RadioGroup.Root
defaultValue={
requiresConfirmation
@ -101,7 +108,8 @@ export default function RequiresConfirmationController({
}
}}>
<div className="flex flex-col flex-wrap justify-start gap-y-2">
{(requiresConfirmationSetup === undefined || !requiresConfirmationLockedProps.disabled) && (
{(requiresConfirmationSetup === undefined ||
!requiresConfirmationLockedProps.disabled) && (
<RadioField
label={t("always_requires_confirmation")}
disabled={requiresConfirmationLockedProps.disabled}
@ -109,7 +117,8 @@ export default function RequiresConfirmationController({
value="always"
/>
)}
{(requiresConfirmationSetup !== undefined || !requiresConfirmationLockedProps.disabled) && (
{(requiresConfirmationSetup !== undefined ||
!requiresConfirmationLockedProps.disabled) && (
<RadioField
disabled={requiresConfirmationLockedProps.disabled}
className="items-center"
@ -138,7 +147,7 @@ export default function RequiresConfirmationController({
val
);
}}
className="border-default !m-0 block w-16 rounded-md text-sm [appearance:textfield]"
className="border-default !m-0 block w-16 rounded-r-none border-r-0 text-sm [appearance:textfield]"
defaultValue={metadata?.requiresConfirmationThreshold?.time || 30}
/>
<label
@ -150,7 +159,7 @@ export default function RequiresConfirmationController({
options={options}
isSearchable={false}
isDisabled={requiresConfirmationLockedProps.disabled}
className="ml-2"
innerClassNames={{ control: "rounded-l-none bg-subtle" }}
onChange={(opt) => {
setRequiresConfirmationSetup({
time:
@ -178,6 +187,7 @@ export default function RequiresConfirmationController({
)}
</div>
</RadioGroup.Root>
</div>
</SettingsToggle>
)}
/>

View File

@ -449,7 +449,8 @@ const EventTypePage = (props: EventTypeSetupProps) => {
availability={availability}
isUpdateMutationLoading={updateMutation.isLoading}
formMethods={formMethods}
disableBorder={tabName === "apps" || tabName === "workflows" || tabName === "webhooks"}
// disableBorder={tabName === "apps" || tabName === "workflows" || tabName === "webhooks"}
disableBorder={true}
currentUserMembership={currentUserMembership}
isUserOrganizationAdmin={props.isUserOrganizationAdmin}>
<Form

View File

@ -255,7 +255,7 @@
"yours": "Your account",
"available_apps": "Available Apps",
"available_apps_lower_case": "Available apps",
"available_apps_desc": "You have no apps installed. View popular apps below and explore more in our <1>App Store</1>",
"available_apps_desc": "View popular apps below and explore more in our <1>App Store</1>",
"fixed_host_helper": "Add anyone who needs to attend the event. <1>Learn more</1>",
"round_robin_helper":"People in the group take turns and only one person will show up for the event.",
"check_email_reset_password": "Check your email. We sent you a link to reset your password.",

View File

@ -6,7 +6,7 @@ import { components } from "react-select";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { DestinationCalendar } from "@calcom/prisma/client";
import { trpc } from "@calcom/trpc/react";
import { Select } from "@calcom/ui";
import { Select, Badge } from "@calcom/ui";
import { Check } from "@calcom/ui/components/icon";
interface Props {
@ -133,9 +133,9 @@ const DestinationCalendarSelector = ({
`${t("create_events_on")}`
) : (
<span className="text-default min-w-0 overflow-hidden truncate whitespace-nowrap">
{t("default_calendar_selected")}{" "}
<Badge variant="blue">Default</Badge>{" "}
{queryDestinationCalendar.name &&
`| ${queryDestinationCalendar.name} (${queryDestinationCalendar?.integrationTitle} - ${queryDestinationCalendar.primaryEmail})`}
`${queryDestinationCalendar.name} (${queryDestinationCalendar?.integrationTitle} - ${queryDestinationCalendar.primaryEmail})`}
</span>
)
}

View File

@ -112,7 +112,7 @@ export const FormBuilder = function FormBuilder({
{LockedIcon}
</div>
<p className="text-subtle max-w-[280px] break-words py-1 text-sm sm:max-w-[500px]">{description}</p>
<ul className="border-default divide-subtle mt-2 divide-y rounded-md border">
<ul className="border-subtle divide-subtle mt-2 divide-y rounded-md border">
{fields.map((field, index) => {
const options = field.options
? field.options
@ -155,7 +155,7 @@ export const FormBuilder = function FormBuilder({
{index >= 1 && (
<button
type="button"
className="bg-default text-muted hover:text-emphasis disabled:hover:text-muted border-default hover:border-emphasis invisible absolute -left-[12px] -ml-4 -mt-4 mb-4 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border p-1 transition-all hover:shadow disabled:hover:border-inherit disabled:hover:shadow-none group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex"
className="bg-default text-muted hover:text-emphasis disabled:hover:text-muted border-subtle hover:border-emphasis invisible absolute -left-[12px] -ml-4 -mt-4 mb-4 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border p-1 transition-all hover:shadow disabled:hover:border-inherit disabled:hover:shadow-none group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex"
onClick={() => swap(index, index - 1)}>
<ArrowUp className="h-5 w-5" />
</button>
@ -163,7 +163,7 @@ export const FormBuilder = function FormBuilder({
{index < fields.length - 1 && (
<button
type="button"
className="bg-default text-muted hover:border-emphasis border-default hover:text-emphasis disabled:hover:text-muted invisible absolute -left-[12px] -ml-4 mt-8 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border p-1 transition-all hover:shadow disabled:hover:border-inherit disabled:hover:shadow-none group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex"
className="bg-default text-muted hover:border-emphasis border-subtle hover:text-emphasis disabled:hover:text-muted invisible absolute -left-[12px] -ml-4 mt-8 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border p-1 transition-all hover:shadow disabled:hover:border-inherit disabled:hover:shadow-none group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex"
onClick={() => swap(index, index + 1)}>
<ArrowDown className="h-5 w-5" />
</button>
@ -628,7 +628,7 @@ function VariantFields({
<ul
className={classNames(
!isSimpleVariant ? "border-default divide-subtle mt-2 divide-y rounded-md border" : ""
!isSimpleVariant ? "border-subtle divide-subtle mt-2 divide-y rounded-md border" : ""
)}>
{variantFields.map((f, index) => {
const rhfVariantFieldPrefix = `variantsConfig.variants.${variantName}.fields.${index}` as const;

View File

@ -15,7 +15,7 @@ import {
Switch,
Tooltip,
} from "@calcom/ui";
import { AlertCircle, Edit, MoreHorizontal, Trash } from "@calcom/ui/components/icon";
import { Edit, MoreHorizontal, Trash, Zap } from "@calcom/ui/components/icon";
type WebhookProps = {
id: string;
@ -87,7 +87,7 @@ export default function WebhookListItem(props: {
key={trigger}
className="mt-2.5 basis-1/5 ltr:mr-2 rtl:ml-2"
variant="gray"
startIcon={AlertCircle}>
startIcon={Zap}>
{t(`${trigger.toLowerCase()}`)}
</Badge>
))}

View File

@ -24,8 +24,19 @@ export const Select = <
menuPlacement,
variant = "default",
...props
}: SelectProps<Option, IsMulti, Group>) => {
const { classNames, ...restProps } = props;
}: SelectProps<Option, IsMulti, Group> & {
innerClassNames?: {
input?: string;
option?: string;
control?: string;
singleValue?: string;
valueContainer?: string;
multiValue?: string;
menu?: string;
menuList?: string;
};
}) => {
const { classNames, innerClassNames, ...restProps } = props;
const reactSelectProps = React.useMemo(() => {
return getReactSelectProps<Option, IsMulti, Group>({
components: components || {},
@ -39,14 +50,14 @@ export const Select = <
<ReactSelect
{...reactSelectProps}
classNames={{
input: () => cx("text-emphasis", props.classNames?.input),
input: () => cx("text-emphasis", innerClassNames?.input),
option: (state) =>
cx(
"bg-default flex cursor-pointer justify-between py-2.5 px-3 rounded-none text-default ",
state.isFocused && "bg-subtle",
state.isDisabled && "bg-muted",
state.isSelected && "bg-emphasis text-default",
props.classNames?.option
innerClassNames?.option
),
placeholder: (state) => cx("text-muted", state.isFocused && variant !== "checkbox" && "hidden"),
dropdownIndicator: () => "text-default",
@ -59,25 +70,25 @@ export const Select = <
: state.hasValue
? "p-1 h-fit"
: "px-3 py-2 h-fit"
: "py-2 px-3 h-fit",
: "py-2 px-3",
props.isDisabled && "bg-subtle",
props.classNames?.control
innerClassNames?.control
),
singleValue: () => cx("text-emphasis placeholder:text-muted", props.classNames?.singleValue),
singleValue: () => cx("text-emphasis placeholder:text-muted", innerClassNames?.singleValue),
valueContainer: () =>
cx("text-emphasis placeholder:text-muted flex gap-1", props.classNames?.valueContainer),
cx("text-emphasis placeholder:text-muted flex gap-1", innerClassNames?.valueContainer),
multiValue: () =>
cx(
"bg-subtle text-default rounded-md py-1.5 px-2 flex items-center text-sm leading-none",
props.classNames?.multiValue
innerClassNames?.multiValue
),
menu: () =>
cx(
"rounded-md bg-default text-sm leading-4 text-default mt-1 border border-subtle",
props.classNames?.menu
innerClassNames?.menu
),
groupHeading: () => "leading-none text-xs uppercase text-default pl-2.5 pt-4 pb-2",
menuList: () => cx("scroll-bar scrollbar-track-w-20 rounded-md", props.classNames?.menuList),
menuList: () => cx("scroll-bar scrollbar-track-w-20 rounded-md", innerClassNames?.menuList),
indicatorsContainer: (state) =>
cx(
state.selectProps.menuIsOpen

View File

@ -1,6 +1,8 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import type { ReactNode } from "react";
import { classNames } from "@calcom/lib";
import { Label } from "..";
import Switch from "./Switch";
@ -11,9 +13,13 @@ type Props = {
checked: boolean;
disabled?: boolean;
LockedIcon?: React.ReactNode;
Badge?: React.ReactNode;
onCheckedChange?: (checked: boolean) => void;
"data-testid"?: string;
tooltip?: string;
toggleSwitchAtTheEnd?: boolean;
childrenClassName?: string;
switchContainerClassName?: string;
};
function SettingsToggle({
@ -21,10 +27,14 @@ function SettingsToggle({
onCheckedChange,
description,
LockedIcon,
Badge,
title,
children,
disabled,
tooltip,
toggleSwitchAtTheEnd = false,
childrenClassName,
switchContainerClassName,
...rest
}: Props) {
const [animateRef] = useAutoAnimate<HTMLDivElement>();
@ -33,6 +43,30 @@ function SettingsToggle({
<>
<div className="flex w-full flex-col space-y-4 lg:flex-row lg:space-x-4 lg:space-y-0">
<fieldset className="block w-full flex-col sm:flex">
{toggleSwitchAtTheEnd ? (
<div className={classNames("flex justify-between space-x-3", switchContainerClassName)}>
<div>
<div className="flex items-center">
<Label className="text-emphasis text-base font-semibold leading-none">
{title}
{LockedIcon}
</Label>
{Badge}
</div>
{description && <p className="text-default -mt-1.5 text-sm leading-normal">{description}</p>}
</div>
<div className="my-auto h-full">
<Switch
data-testid={rest["data-testid"]}
fitToHeight={true}
checked={checked}
onCheckedChange={onCheckedChange}
disabled={disabled}
tooltip={tooltip}
/>
</div>
</div>
) : (
<div className="flex space-x-3">
<Switch
data-testid={rest["data-testid"]}
@ -51,9 +85,10 @@ function SettingsToggle({
{description && <p className="text-default -mt-1.5 text-sm leading-normal">{description}</p>}
</div>
</div>
)}
{children && (
<div className="lg:ml-14" ref={animateRef}>
{checked && <div className="mt-4">{children}</div>}
<div className={classNames("lg:ml-14", childrenClassName)} ref={animateRef}>
{checked && <div className={classNames(!toggleSwitchAtTheEnd && "mt-4")}>{children}</div>}
</div>
)}
</fieldset>