diff --git a/apps/web/components/v2/eventtype/EventAdvancedTab.tsx b/apps/web/components/v2/eventtype/EventAdvancedTab.tsx index 336364d631..323f2b1134 100644 --- a/apps/web/components/v2/eventtype/EventAdvancedTab.tsx +++ b/apps/web/components/v2/eventtype/EventAdvancedTab.tsx @@ -18,6 +18,7 @@ import { Dialog, DialogContent, Label, + SettingsToggle, showToast, Skeleton, Switch, @@ -43,7 +44,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick( eventType.customInputs.sort((a, b) => a.id - b.id) || [] ); @@ -53,8 +53,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick(); - const removeCustom = (index: number) => { formMethods.getValues("customInputs").splice(index, 1); customInputs.splice(index, 1); @@ -120,80 +118,61 @@ export const EventAdvancedTab = ({ eventType, team }: Pick
-
- 0} - fitToHeight={true} - onCheckedChange={(e) => { - if (e && customInputs.length === 0) { - // Push a placeholders - setSelectedCustomInput(undefined); - setSelectedCustomInputModalOpen(true); - } else if (!e) { - setCustomInputs([]); - formMethods.setValue("customInputs", []); - } - }} - /> -
- - {t("additional_inputs")} - - - {t("additional_input_description")} - -
-
-
    - {customInputs.map((customInput: EventTypeCustomInput, idx: number) => ( - { - setSelectedCustomInput(customInput); - setSelectedCustomInputModalOpen(true); - }} - deleteOnClick={() => removeCustom(idx)} - /> - ))} -
- {customInputs.length > 0 && ( - - )} + } else if (!e) { + setCustomInputs([]); + formMethods.setValue("customInputs", []); + } + }}> +
    + {customInputs.map((customInput: EventTypeCustomInput, idx: number) => ( + { + setSelectedCustomInput(customInput); + setSelectedCustomInputModalOpen(true); + }} + deleteOnClick={() => removeCustom(idx)} + /> + ))} +
+ {customInputs.length > 0 && ( + + )} +

( -
- onChange(e)} - disabled={seatsEnabled} - fitToHeight={true} - /> -
- - {t("requires_confirmation")} - - - {t("requires_confirmation_description")} - -
-
+ onChange(e)} + disabled={seatsEnabled} + /> )} />
@@ -202,23 +181,13 @@ export const EventAdvancedTab = ({ eventType, team }: Pick ( -
- onChange(e)} - disabled={seatsEnabled} - /> -
- - {t("disable_guests")} - - - {t("disable_guests_description")} - -
-
+ onChange(e)} + disabled={seatsEnabled} + /> )} /> @@ -228,22 +197,12 @@ export const EventAdvancedTab = ({ eventType, team }: Pick ( -
- onChange(e)} - /> -
- - {t("disable_notes")} - - - {t("disable_notes_description")} - -
-
+ onChange(e)} + /> )} />
@@ -253,20 +212,12 @@ export const EventAdvancedTab = ({ eventType, team }: Pick (
- onChange(e)} /> -
- - {t("require_additional_notes")} - - - {t("require_additional_notes_description")} - -
)} /> @@ -276,29 +227,19 @@ export const EventAdvancedTab = ({ eventType, team }: Pick ( <> -
- { - setRedirectUrlVisible(e); - onChange(e ? value : ""); - }} - /> -
- - {t("redirect_success_booking")} - - - {t("redirect_url_description")} - -
-
- {redirectUrlVisible && ( -
+ { + setRedirectUrlVisible(e); + onChange(e ? value : ""); + }}> + {/* Textfield has some margin by default we remove that so we can keep consitant aligment */} +
- )} +
)} />
- ( - <> -
- { - setHashedLinkVisible(e); - onChange(e ? hashedUrl : undefined); - }} - /> -
- - {t("private_link")} - - - {t("private_link_description")} - -
-
- {hashedLinkVisible && ( -
- - - - } - /> -
- )} - - )} - /> + { + formMethods.setValue("hashedLink", e ? hashedUrl : undefined); + setHashedLinkVisible(e); + }}> + {/* Textfield has some margin by default we remove that so we can keep consitant aligment */} +
+ + + + } + /> +
+

( -
- { - setSeatsInputVisible(e); - // Enabling seats will disable guests and requiring confimation until fully supported - if (e) { - formMethods.setValue("disableGuests", true); - formMethods.setValue("requiresConfirmation", false); - formMethods.setValue("seatsPerTimeSlot", 2); - } else { - formMethods.setValue("seatsPerTimeSlot", null); - } - onChange(e); - }} - fitToHeight={true} + { + // Enabling seats will disable guests and requiring confimation until fully supported + if (e) { + formMethods.setValue("disableGuests", true); + formMethods.setValue("requiresConfirmation", false); + formMethods.setValue("seatsPerTimeSlot", 2); + } else { + formMethods.setValue("seatsPerTimeSlot", null); + formMethods.setValue("disableGuests", false); + } + onChange(e); + }}> + ( +
+ {t("seats")}} + onChange={(e) => { + onChange(Number(e.target.value)); + }} + /> +
+ formMethods.setValue("seatsShowAttendees", e.target.checked)} + defaultChecked={!!eventType.seatsShowAttendees} + /> +
+
+ )} /> -
- - {t("offer_seats")} - - - {t("offer_seats_description")} - -
-
+ )} /> - {seatsInputVisible && ( - ( -
- {t("seats")}} - onChange={(e) => { - onChange(Number(e.target.value)); - }} - /> -
- formMethods.setValue("seatsShowAttendees", e.target.checked)} - defaultChecked={!!eventType.seatsShowAttendees} - /> -
-
- )} - /> - )} + {showEventNameTip && ( ) => { @@ -159,149 +159,106 @@ export const EventLimitsTab = (props: Pick)
-
-
-
- ( - 0} - onCheckedChange={(active) => { - if (active) { - formMethods.setValue("bookingLimits", { - PER_DAY: 1, - }); - } else { - formMethods.setValue("bookingLimits", undefined); - } - }} - /> - )} - /> - -
- -

- {t("limit_booking_frequency_description")} -

-
-
-
+ ( + 0} + onCheckedChange={(active) => { + if (active) { + formMethods.setValue("bookingLimits", { + PER_DAY: 1, + }); + } else { + formMethods.setValue("bookingLimits", {}); + } + }}> -
-
-
+ + )} + />
- -
-
-
- ( - - formMethods.setValue("periodType", bool ? "ROLLING" : "UNLIMITED") - } - /> - )} - /> - -
- -

- Limit how far in the future people can book a time -

-
-
-
- ( - formMethods.setValue("periodType", val as PeriodType)}> - {PERIOD_TYPES.map((period) => { - if (period.type === "UNLIMITED") return null; - return ( -
- - - - {period.prefix ? {period.prefix}  : null} - {period.type === "ROLLING" && ( -
- - -
- )} - {period.type === "RANGE" && ( -
- ( - { - formMethods.setValue("periodDates", { - startDate, - endDate, - }); - }} - /> - )} - /> -
- )} - {period.suffix ? ( -  {period.suffix} - ) : null} + ( + formMethods.setValue("periodType", bool ? "ROLLING" : "UNLIMITED")}> + formMethods.setValue("periodType", val as PeriodType)}> + {PERIOD_TYPES.map((period) => { + if (period.type === "UNLIMITED") return null; + return ( +
+ + + + {period.prefix ? {period.prefix}  : null} + {period.type === "ROLLING" && ( +
+ +
- ); - })} - - )} - /> -
-
-
+ )} + {period.type === "RANGE" && ( +
+ ( + { + formMethods.setValue("periodDates", { + startDate, + endDate, + }); + }} + /> + )} + /> +
+ )} + {period.suffix ?  {period.suffix} : null} +
+ ); + })} + +
+ )} + /> ); }; diff --git a/apps/web/components/v2/eventtype/RecurringEventController.tsx b/apps/web/components/v2/eventtype/RecurringEventController.tsx index f81d35a6a7..b5a1b91902 100644 --- a/apps/web/components/v2/eventtype/RecurringEventController.tsx +++ b/apps/web/components/v2/eventtype/RecurringEventController.tsx @@ -6,7 +6,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale"; import { Frequency } from "@calcom/prisma/zod-utils"; import type { RecurringEvent } from "@calcom/types/Calendar"; import { Alert } from "@calcom/ui/Alert"; -import { Label, Select, Switch } from "@calcom/ui/v2"; +import { Label, Select, Switch, SettingsToggle } from "@calcom/ui/v2"; type RecurringEventControllerProps = { recurringEvent: RecurringEvent | null; @@ -36,95 +36,85 @@ export default function RecurringEventController({ ) : ( <> -
- { - if (!e) { - formMethods.setValue("recurringEvent", null); - setRecurringEventState(null); - } else { - const newVal = recurringEvent || { - interval: 1, - count: 12, - freq: Frequency.WEEKLY, - }; - formMethods.setValue("recurringEvent", newVal); - setRecurringEventState(newVal); - } - }} - /> -
- -

- {t("recurring_event_description")} -

- {recurringEventState && ( -
-
-

{t("repeats_every")}

- { - const newVal = { - ...recurringEventState, - interval: parseInt(event?.target.value), - }; - formMethods.setValue("recurringEvent", newVal); - setRecurringEventState(newVal); - }} - /> - { - const newVal = { - ...recurringEventState, - count: parseInt(event?.target.value), - }; - formMethods.setValue("recurringEvent", newVal); - setRecurringEventState(newVal); - }} - /> -

- {t("events", { - count: recurringEventState.count, - })} -

-
+ { + if (!e) { + formMethods.setValue("recurringEvent", null); + setRecurringEventState(null); + } else { + const newVal = recurringEvent || { + interval: 1, + count: 12, + freq: Frequency.WEEKLY, + }; + formMethods.setValue("recurringEvent", newVal); + setRecurringEventState(newVal); + } + }}> + {recurringEventState && ( +
+
+

{t("repeats_every")}

+ { + const newVal = { + ...recurringEventState, + interval: parseInt(event?.target.value), + }; + formMethods.setValue("recurringEvent", newVal); + setRecurringEventState(newVal); + }} + /> + { + const newVal = { + ...recurringEventState, + count: parseInt(event?.target.value), + }; + formMethods.setValue("recurringEvent", newVal); + setRecurringEventState(newVal); + }} + /> +

+ {t("events", { + count: recurringEventState.count, + })} +

+
+
+ )} +
)}
diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 7b11851a85..e5e4fd890d 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -1327,6 +1327,8 @@ "saml_sp_entity_id_copied": "SP Entity ID copied!", "saml_btn_configure": "Configure", "add_calendar": "Add Calendar", + "limit_future_bookings":"Limit future bookings", + "limit_future_bookings_description":"Limit how far in the future this event can be booked", "no_event_types": "No event types setup", "no_event_types_description": "{{name}} has not setup any event types for you to book." } diff --git a/packages/ui/package.json b/packages/ui/package.json index 7cc085b4d3..3a96d8169d 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -23,6 +23,7 @@ "react": "^18.2.0", "react-colorful": "^5.6.0", "react-feather": "^2.0.10", + "@formkit/auto-animate": "^1.0.0-beta.1", "react-hook-form": "^7.34.2", "react-icons": "^4.4.0", "react-select": "^5.4.0" diff --git a/packages/ui/v2/core/SettingsToggle.tsx b/packages/ui/v2/core/SettingsToggle.tsx new file mode 100644 index 0000000000..a81d873bc5 --- /dev/null +++ b/packages/ui/v2/core/SettingsToggle.tsx @@ -0,0 +1,57 @@ +import { useAutoAnimate } from "@formkit/auto-animate/react"; +import React, { ReactNode } from "react"; + +import Switch from "./Switch"; +import { Label } from "./form"; + +type Props = { + children?: ReactNode; + title: string; + description?: string; + checked: boolean; + disabled?: boolean; + onCheckedChange?: (checked: boolean) => void; + "data-testid"?: string; +}; + +function SettingsToggle({ + checked, + onCheckedChange, + description, + title, + children, + disabled, + ...rest +}: Props) { + const [animateRef] = useAutoAnimate(); + + return ( + <> +
+
+
+ + +
+ + {description &&

{description}

} +
+
+ {children && ( +
+ {checked && children} +
+ )} +
+
+ + ); +} + +export default SettingsToggle; diff --git a/packages/ui/v2/core/index.ts b/packages/ui/v2/core/index.ts index 1a7feb071c..e7a22dbd93 100644 --- a/packages/ui/v2/core/index.ts +++ b/packages/ui/v2/core/index.ts @@ -31,3 +31,4 @@ export { default as HorizontalTabs, HorizontalTabItem } from "./navigation/tabs/ export type { VerticalTabItemProps } from "./navigation/tabs/VerticalTabItem"; export type { HorizontalTabItemProps } from "./navigation/tabs/HorizontalTabItem"; export * from "./Portal"; +export { default as SettingsToggle } from "./SettingsToggle"; diff --git a/packages/ui/v2/modules/event-types/CustomInputItem.tsx b/packages/ui/v2/modules/event-types/CustomInputItem.tsx index 501857195a..227d3109e3 100644 --- a/packages/ui/v2/modules/event-types/CustomInputItem.tsx +++ b/packages/ui/v2/modules/event-types/CustomInputItem.tsx @@ -15,7 +15,7 @@ type Props = { function CustomInputItem({ required, deleteOnClick, editOnClick, type, question }: Props) { const { t } = useLocale(); return ( -
  • +
  • {question}