From eb4eebe6f97fe72f11cd913a4dad7f2b791a48a4 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 15:28:09 +0530 Subject: [PATCH] feat: expose form validation methods from EventTypePlatformWrapper (#22655) * feat: expose form validation methods from EventTypePlatformWrapper - Add validateForm and handleFormSubmit methods to onFormStateChange prop - Update useEventTypeForm hook to return validateForm method - Add proper TypeScript types for form validation result - Enable external form validation and submission capabilities Co-Authored-By: somay@cal.com * fix: correct forwardRef syntax to resolve parsing errors - Fix forwardRef parameter destructuring syntax - Use proper props parameter pattern for forwardRef components - Ensure prettier can parse the file correctly Co-Authored-By: somay@cal.com * feat: add form validation and submission handlers to event types platform wrapper * refactor: move form validation and submit handlers from hook to wrapper component * fix: add ref to the save button for proper form validation * feat: extend ref-based validation API to AvailabilitySettingsPlatformWrapper - Add AvailabilityFormValidationResult and AvailabilitySettingsPlatformWrapperRef types - Implement forwardRef pattern in AvailabilitySettings component with save button ref - Add validateForm and handleFormSubmit methods using useImperativeHandle - Update AvailabilitySettingsPlatformWrapper to support ref forwarding - Add comprehensive documentation for ref-based validation API in both event-type.mdx and availability-settings.mdx - Follow same patterns established in EventTypePlatformWrapper for consistency Co-Authored-By: somay@cal.com * refactor: rename AvailabilitySettingsPlatformWrapperRef to AvailabilitySettingsFormRef * feat: add ref-based validation pattern to availability example page - Mirror the ref-based validation implementation from event-types.tsx - Add useRef, handleValidate, and handleSubmit functions - Demonstrate validateForm() and handleFormSubmit() methods - Add validation and submit buttons to showcase the new API - Use AvailabilitySettingsFormRef type for proper typing Co-Authored-By: somay@cal.com * refactor: remove unused state variables * docs: clarify form validation behavior for availability and event type atoms * chore: remove console.logs --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: somay@cal.com Co-authored-by: Somay Chauhan --- docs/platform/atoms/availability-settings.mdx | 56 ++ docs/platform/atoms/event-type.mdx | 54 ++ .../eventtypes/components/EventType.tsx | 5 +- .../eventtypes/components/EventTypeLayout.tsx | 3 + packages/features/eventtypes/lib/types.ts | 10 + .../availability/AvailabilitySettings.tsx | 809 ++++++++++-------- packages/platform/atoms/availability/types.ts | 10 + .../AvailabilitySettingsPlatformWrapper.tsx | 46 +- .../event-types/hooks/useEventTypeForm.ts | 20 +- .../wrappers/EventTypePlatformWrapper.tsx | 120 ++- .../atoms/event-types/wrappers/types.ts | 3 + packages/platform/atoms/index.ts | 2 + packages/platform/atoms/package.json | 6 +- .../examples/base/src/pages/availability.tsx | 34 + .../examples/base/src/pages/event-types.tsx | 32 +- 15 files changed, 755 insertions(+), 455 deletions(-) diff --git a/docs/platform/atoms/availability-settings.mdx b/docs/platform/atoms/availability-settings.mdx index d792b09a8c..79a7590e8e 100644 --- a/docs/platform/atoms/availability-settings.mdx +++ b/docs/platform/atoms/availability-settings.mdx @@ -133,3 +133,59 @@ Along with the props, Availability settings atom accepts custom styles via the * | overridesModalClassNames | Adds styling to the date overrides modal | Please ensure all custom classnames are valid [Tailwind CSS](https://tailwindcss.com/) classnames. + +## Programmatic Form Validation and Submission + +The AvailabilitySettings component supports programmatic form validation and submission through a ref-based API. This allows you to validate availability data and submit forms programmatically without user interaction. + +```js +import { useRef } from 'react'; +import { AvailabilitySettings } from "@calcom/atoms"; +import type { AvailabilitySettingsFormRef } from "@calcom/atoms"; + +export function AvailabilityWithValidation() { + const availabilityRef = useRef(null); + + const handleValidate = async () => { + const result = await availabilityRef.current?.validateForm(); + if (result?.isValid) { + console.log("Availability form is valid"); + } else { + console.log("Validation errors:", result?.errors); + } + }; + + const handleSubmit = () => { + availabilityRef.current?.handleFormSubmit(); + }; + + return ( + <> + { + console.log("Updated schedule successfully"); + }} + onFormStateChange={(formState) => { + console.log("Form state changed:", formState); + }} + /> + + + + ); +} +``` + +### Ref Methods + +| Method | Description | +|:-------|:------------| +| validateForm | Validates the current availability form state and returns a promise with validation results. | +| handleFormSubmit | Programmatically submits the availability form, triggering the same validation and submission flow as clicking the save button. Unlike `validateForm`, this method will check required fields and prevent submission unless all required fields are set | + +The `validateForm` method returns an `AvailabilityFormValidationResult` object with: +- `isValid`: Boolean indicating if the form passed validation +- `errors`: Object containing any validation errors found + +**Note:** If a required field is not filled in, the `validateForm` method will not return any error. The validation focuses on the format and consistency of provided data rather than enforcing required field completion. diff --git a/docs/platform/atoms/event-type.mdx b/docs/platform/atoms/event-type.mdx index 8b4ce017a1..aa8ab19540 100644 --- a/docs/platform/atoms/event-type.mdx +++ b/docs/platform/atoms/event-type.mdx @@ -196,6 +196,60 @@ Along with the props, event type settings atom accepts custom styles via the **c Please ensure all custom classnames are valid [Tailwind CSS](https://tailwindcss.com/) classnames. +## Programmatic Form Validation and Submission + +The EventTypeSettings component supports programmatic form validation and submission through a ref-based API. This allows you to validate form data and submit forms programmatically without user interaction. + +```js +import { useRef } from 'react'; +import { EventTypeSettings } from "@calcom/atoms"; +import type { EventTypePlatformWrapperRef } from "@calcom/atoms"; + +export function EventTypeWithValidation(eventTypeId: number) { + const eventTypeRef = useRef(null); + + const handleValidate = async () => { + const result = await eventTypeRef.current?.validateForm(); + if (result?.isValid) { + console.log("Form is valid"); + } else { + console.log("Validation errors:", result?.errors); + } + }; + + const handleSubmit = () => { + eventTypeRef.current?.handleFormSubmit(); + }; + + return ( + <> + { + console.log("EventType updated successfully", eventType); + }} + /> + + + + ); +} +``` + +### Ref Methods + +| Method | Description | +|:-------|:------------| +| validateForm | Validates the current event type form state and returns a promise with validation results. | +| handleFormSubmit | Programmatically submits the event type form, triggering the same validation and submission flow as clicking the save button. Unlike `validateForm`, this method will check required fields and prevent submission unless all required fields are set | + +The `validateForm` method returns an `EventTypeFormValidationResult` object with: +- `isValid`: Boolean indicating if the form passed validation +- `errors`: Object containing any validation errors found + +**Note:** If a required field is not filled in, the `validateForm` method will not return any error. The validation focuses on the format and consistency of provided data rather than enforcing required field completion. + Following are the tabs that are available in the event type settings atom:

diff --git a/packages/features/eventtypes/components/EventType.tsx b/packages/features/eventtypes/components/EventType.tsx index db0fc96495..ff7239b440 100644 --- a/packages/features/eventtypes/components/EventType.tsx +++ b/packages/features/eventtypes/components/EventType.tsx @@ -64,6 +64,7 @@ export type EventTypeComponentProps = EventTypeSetupProps & { tabName: (typeof tabs)[number]; tabsNavigation: VerticalTabItemProps[]; allowDelete?: boolean; + saveButtonRef?: React.RefObject; }; export const EventType = ({ @@ -82,6 +83,7 @@ export const EventType = ({ handleSubmit, children, allowDelete = true, + saveButtonRef, }: EventTypeComponentProps) => { const [animationParentRef] = useAutoAnimate(); @@ -101,7 +103,8 @@ export const EventType = ({ isDeleting={isDeleting} isPlatform={isPlatform} allowDelete={allowDelete} - tabsNavigation={tabsNavigation}> + tabsNavigation={tabsNavigation} + saveButtonRef={saveButtonRef}>
{tabMap[tabName]}
diff --git a/packages/features/eventtypes/components/EventTypeLayout.tsx b/packages/features/eventtypes/components/EventTypeLayout.tsx index 918bac27ef..20d4cb4610 100644 --- a/packages/features/eventtypes/components/EventTypeLayout.tsx +++ b/packages/features/eventtypes/components/EventTypeLayout.tsx @@ -47,6 +47,7 @@ type Props = { isPlatform?: boolean; tabsNavigation: VerticalTabItemProps[]; allowDelete?: boolean; + saveButtonRef?: React.RefObject; }; function EventTypeSingleLayout({ @@ -64,6 +65,7 @@ function EventTypeSingleLayout({ isPlatform, tabsNavigation, allowDelete = true, + saveButtonRef, }: Props) { const { t } = useLocale(); const eventTypesLockedByOrg = eventType.team?.parent?.organizationSettings?.lockEventTypeCreationForUsers; @@ -262,6 +264,7 @@ function EventTypeSingleLayout({
-
- {allowSetToDefault && ( - <> - - {t("set_to_default")} - - ( - - )} - /> - - )} -
- -
-
-
- - {t("timezone")} - - - value ? ( - onChange(timezone.value)} - /> - ) : ( - - ) - } - /> -
- {!isPlatform && ( +
+
+ + {t("name")} + + ( + + )} + /> +
+
+ {allowSetToDefault && ( <> -
-
- - {t("something_doesnt_look_right")} - -
- - {t("launch_troubleshooter")} - -
-
+ + {t("set_to_default")} + + ( + + )} + /> )}
+ +
+
+
+ + {t("timezone")} + + + value ? ( + onChange(timezone.value)} + /> + ) : ( + + ) + } + /> +
+ {!isPlatform && ( + <> +
+
+ + {t("something_doesnt_look_right")} + +
+ + {t("launch_troubleshooter")} + +
+
+ + )} +
+
+ + +
+ +
+ }> +
+
{ + handleSubmit(props); + }} + className={cn(customClassNames?.formClassName, "flex flex-col sm:mx-0 xl:flex-row xl:space-x-6")}> +
+
+
+ {typeof weekStart === "string" && ( + + )} +
- - -
- -
- }> -
- { - handleSubmit(props); - }} - className={cn(customClassNames?.formClassName, "flex flex-col sm:mx-0 xl:flex-row xl:space-x-6")}> -
-
-
- {typeof weekStart === "string" && ( - + )} +
+
+
+
+ + {t("timezone")} + + + value ? ( + onChange(timezone.value)} + /> + ) : ( + + ) } /> +
+ {isPlatform ? ( + <> + ) : ( + <> +
+
+ + {t("something_doesnt_look_right")} + +
+ + {t("launch_troubleshooter")} + +
+
+ )}
- {enableOverrides && ( - - )} -
-
-
-
- - {t("timezone")} - - - value ? ( - onChange(timezone.value)} - /> - ) : ( - - ) - } - /> -
- {isPlatform ? ( - <> - ) : ( - <> -
-
- - {t("something_doesnt_look_right")} - -
- - {t("launch_troubleshooter")} - -
-
- - )} -
-
- -
- - ); -} + +
+ + ); + } +); diff --git a/packages/platform/atoms/availability/types.ts b/packages/platform/atoms/availability/types.ts index 140b1dbd68..41fa969e80 100644 --- a/packages/platform/atoms/availability/types.ts +++ b/packages/platform/atoms/availability/types.ts @@ -29,3 +29,13 @@ export type scheduleClassNames = { labelAndSwitchContainer?: string; scheduleContainer?: string; }; + +export type AvailabilityFormValidationResult = { + isValid: boolean; + errors: Record; +}; + +export interface AvailabilitySettingsFormRef { + validateForm: () => Promise; + handleFormSubmit: () => void; +} diff --git a/packages/platform/atoms/availability/wrappers/AvailabilitySettingsPlatformWrapper.tsx b/packages/platform/atoms/availability/wrappers/AvailabilitySettingsPlatformWrapper.tsx index d4c3a9d2c7..9ebc101fd9 100644 --- a/packages/platform/atoms/availability/wrappers/AvailabilitySettingsPlatformWrapper.tsx +++ b/packages/platform/atoms/availability/wrappers/AvailabilitySettingsPlatformWrapper.tsx @@ -1,4 +1,5 @@ import type { ReactNode } from "react"; +import { forwardRef } from "react"; import type { ScheduleLabelsType } from "@calcom/features/schedules/components/Schedule"; import type { UpdateScheduleResponse } from "@calcom/lib/schedules/updateSchedule"; @@ -11,7 +12,7 @@ import { useMe } from "../../hooks/useMe"; import { AtomsWrapper } from "../../src/components/atoms-wrapper"; import { useToast } from "../../src/components/ui/use-toast"; import type { Availability } from "../AvailabilitySettings"; -import type { CustomClassNames } from "../AvailabilitySettings"; +import type { CustomClassNames, AvailabilitySettingsFormRef } from "../AvailabilitySettings"; import { AvailabilitySettings } from "../AvailabilitySettings"; import type { AvailabilityFormValues } from "../types"; @@ -37,24 +38,28 @@ export type AvailabilitySettingsPlatformWrapperProps = { loadingStateChildren?: ReactNode; }; -export const AvailabilitySettingsPlatformWrapper = ({ - id, - customClassNames, - onDeleteError, - onDeleteSuccess, - onUpdateError, - onUpdateSuccess, - disableEditableHeading = false, - enableOverrides = false, - onBeforeUpdate, - onFormStateChange, - allowDelete, - allowSetToDefault, - disableToasts, - isDryRun = false, - noScheduleChildren, - loadingStateChildren, -}: AvailabilitySettingsPlatformWrapperProps) => { +export const AvailabilitySettingsPlatformWrapper = forwardRef< + AvailabilitySettingsFormRef, + AvailabilitySettingsPlatformWrapperProps +>(function AvailabilitySettingsPlatformWrapper(props, ref) { + const { + id, + customClassNames, + onDeleteError, + onDeleteSuccess, + onUpdateError, + onUpdateSuccess, + disableEditableHeading = false, + enableOverrides = false, + onBeforeUpdate, + onFormStateChange, + allowDelete, + allowSetToDefault, + disableToasts, + isDryRun = false, + noScheduleChildren, + loadingStateChildren, + } = props; const { isLoading, data: atomSchedule } = useAtomSchedule(id); const { data: me } = useMe(); const { timeFormat } = me?.data || { timeFormat: null }; @@ -139,6 +144,7 @@ export const AvailabilitySettingsPlatformWrapper = ({ return ( { if (isDryRun) { @@ -195,4 +201,4 @@ export const AvailabilitySettingsPlatformWrapper = ({ /> ); -}; +}); diff --git a/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts b/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts index e9279a2c32..6abcfc9244 100644 --- a/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts +++ b/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts @@ -196,16 +196,6 @@ export const useEventTypeForm = ({ // Watch all form values to trigger onFormStateChange on any change const watchedValues = form.watch(); - useEffect(() => { - if (onFormStateChange) { - onFormStateChange({ - isDirty: isFormDirty, - dirtyFields: dirtyFields as Partial, - values: watchedValues, - }); - } - }, [isFormDirty, watchedValues, onFormStateChange]); - const isObject = (value: T): boolean => { return value !== null && typeof value === "object" && !Array.isArray(value); }; @@ -421,5 +411,15 @@ export const useEventTypeForm = ({ } }; + useEffect(() => { + if (onFormStateChange) { + onFormStateChange({ + isDirty: isFormDirty, + dirtyFields: dirtyFields as Partial, + values: watchedValues, + }); + } + }, [isFormDirty, dirtyFields, watchedValues, onFormStateChange]); + return { form, handleSubmit }; }; diff --git a/packages/platform/atoms/event-types/wrappers/EventTypePlatformWrapper.tsx b/packages/platform/atoms/event-types/wrappers/EventTypePlatformWrapper.tsx index 162b82f1e6..d589e7d731 100644 --- a/packages/platform/atoms/event-types/wrappers/EventTypePlatformWrapper.tsx +++ b/packages/platform/atoms/event-types/wrappers/EventTypePlatformWrapper.tsx @@ -1,7 +1,7 @@ "use client"; import { useQueryClient } from "@tanstack/react-query"; -import { useRef, useState, useEffect } from "react"; +import { useRef, useState, useEffect, forwardRef, useImperativeHandle, useCallback } from "react"; import type { ChildrenEventType } from "@calcom/features/eventtypes/components/ChildrenEventTypeSelect"; import { EventType as EventTypeComponent } from "@calcom/features/eventtypes/components/EventType"; @@ -12,7 +12,11 @@ import type { EventAvailabilityTabCustomClassNames } from "@calcom/features/even import type { EventLimitsTabCustomClassNames } from "@calcom/features/eventtypes/components/tabs/limits/EventLimitsTab"; import type { EventRecurringTabCustomClassNames } from "@calcom/features/eventtypes/components/tabs/recurring/RecurringEventController"; import type { EventSetupTabCustomClassNames } from "@calcom/features/eventtypes/components/tabs/setup/EventSetupTab"; -import type { EventTypeSetupProps, FormValues } from "@calcom/features/eventtypes/lib/types"; +import type { + EventTypeSetupProps, + FormValues, + EventTypePlatformWrapperRef, +} from "@calcom/features/eventtypes/lib/types"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { SchedulingType } from "@calcom/prisma/enums"; @@ -64,28 +68,30 @@ export type EventTypePlatformWrapperProps = { }) => void; }; -const EventType = ({ - tabs = ["setup", "availability", "team", "limits", "advanced", "recurring", "payments"], - onSuccess, - onError, - onDeleteSuccess, - onDeleteError, - id, - allowDelete = true, - customClassNames, - disableToasts = false, - isDryRun = false, - onFormStateChange, - ...props -}: EventTypeSetupProps & EventTypePlatformWrapperProps) => { +const EventType = forwardRef< + EventTypePlatformWrapperRef, + EventTypeSetupProps & EventTypePlatformWrapperProps +>(function EventType(props, ref) { + const { + tabs = ["setup", "availability", "team", "limits", "advanced", "recurring", "payments"], + onSuccess, + onError, + onDeleteSuccess, + onDeleteError, + id, + allowDelete = true, + customClassNames, + disableToasts = false, + isDryRun = false, + onFormStateChange, + ...restProps + } = props; const { t } = useLocale(); const { toast } = useToast(); const { organizationId } = useAtomsContext(); const isTeamEventTypeDeleted = useRef(false); const leaveWithoutAssigningHosts = useRef(false); - const [isOpenAssignmentWarnDialog, setIsOpenAssignmentWarnDialog] = useState(false); - const [pendingRoute, setPendingRoute] = useState(""); - const { eventType, locationOptions, team, teamMembers, destinationCalendar } = props; + const { eventType, locationOptions, team, teamMembers, destinationCalendar } = restProps; const [slugExistsChildrenDialogOpen, setSlugExistsChildrenDialogOpen] = useState([]); const { data: user, isLoading: isUserLoading } = useMe(); @@ -93,7 +99,6 @@ const EventType = ({ showToast(t("event_type_deleted_successfully"), "success"); isTeamEventTypeDeleted.current = true; setSlugExistsChildrenDialogOpen([]); - setIsOpenAssignmentWarnDialog(false); onDeleteSuccess?.(); }; @@ -158,9 +163,37 @@ const EventType = ({ }, onFormStateChange: onFormStateChange, }); + + // Create a ref for the save button to trigger its click + const saveButtonRef = useRef(null); + + const handleFormSubmit = useCallback(() => { + if (saveButtonRef.current) { + saveButtonRef.current.click(); + } else { + form.handleSubmit(handleSubmit)(); + } + }, [handleSubmit, form]); + + const validateForm = useCallback(async () => { + const isValid = await form.trigger(); + return { + isValid, + errors: form.formState.errors, + }; + }, [form]); + + useImperativeHandle( + ref, + () => ({ + validateForm, + handleFormSubmit, + }), + [validateForm, handleFormSubmit] + ); const slug = form.watch("slug") ?? eventType.slug; - const showToast = (message: string, variant: "success" | "warning" | "error") => { + const showToast = (message: string, _variant: "success" | "warning" | "error") => { if (!disableToasts) { toast({ description: message }); } @@ -246,9 +279,8 @@ const EventType = ({ hosts: eventType.hosts, assignAllTeamMembers: eventType.assignAllTeamMembers, isManagedEventType: eventType.schedulingType === SchedulingType.MANAGED, - onError: (url) => { - setIsOpenAssignmentWarnDialog(true); - setPendingRoute(url); + onError: () => { + return; }, onStart: () => { return; @@ -284,7 +316,7 @@ const EventType = ({ return ( + allowDelete={allowDelete} + saveButtonRef={saveButtonRef}> <> {slugExistsChildrenDialogOpen.length ? ( ); -}; +}); -export const EventTypePlatformWrapper = ({ - id, - tabs, - onSuccess, - onError, - onDeleteSuccess, - onDeleteError, - allowDelete = true, - customClassNames, - isDryRun, - onFormStateChange, -}: EventTypePlatformWrapperProps) => { +export const EventTypePlatformWrapper = forwardRef< + EventTypePlatformWrapperRef, + EventTypePlatformWrapperProps +>(function EventTypePlatformWrapper(props, ref) { + const { + id, + tabs, + onSuccess, + onError, + onDeleteSuccess, + onDeleteError, + allowDelete = true, + customClassNames, + isDryRun, + onFormStateChange, + } = props; const { data: eventTypeQueryData } = useAtomsEventTypeById(id); const queryClient = useQueryClient(); @@ -344,7 +381,7 @@ export const EventTypePlatformWrapper = ({ }); } }; - }, [queryClient, id]); + }, [queryClient, id, eventTypeQueryData]); if (!eventTypeQueryData) return null; @@ -361,6 +398,7 @@ export const EventTypePlatformWrapper = ({ customClassNames={customClassNames} isDryRun={isDryRun} onFormStateChange={onFormStateChange} + ref={ref} /> ); -}; +}); diff --git a/packages/platform/atoms/event-types/wrappers/types.ts b/packages/platform/atoms/event-types/wrappers/types.ts index 67ad4c3e81..54b1de2dd5 100644 --- a/packages/platform/atoms/event-types/wrappers/types.ts +++ b/packages/platform/atoms/event-types/wrappers/types.ts @@ -1,3 +1,6 @@ import type { TabMap } from "@calcom/features/eventtypes/lib/types"; +import type { EventTypePlatformWrapperRef as EventSettingsFromRef } from "@calcom/features/eventtypes/lib/types"; + +export type { EventSettingsFromRef }; export type PlatformTabs = keyof Omit; diff --git a/packages/platform/atoms/index.ts b/packages/platform/atoms/index.ts index 41409e3a38..9cad136808 100644 --- a/packages/platform/atoms/index.ts +++ b/packages/platform/atoms/index.ts @@ -29,6 +29,8 @@ export { DestinationCalendarSettingsPlatformWrapper as DestinationCalendarSettin export { CalendarSettingsPlatformWrapper as CalendarSettings } from "./calendar-settings/index"; export type { UpdateScheduleInput_2024_06_11 as UpdateScheduleBody } from "@calcom/platform-types"; export { EventTypePlatformWrapper as EventTypeSettings } from "./event-types/wrappers/EventTypePlatformWrapper"; +export type { EventSettingsFromRef } from "./event-types/wrappers/types"; +export type { AvailabilitySettingsFormRef } from "./availability/types"; export { ConferencingAppsViewPlatformWrapper as ConferencingAppsSettings } from "./connect/conferencing-apps/ConferencingAppsViewPlatformWrapper"; export { StripeConnect } from "./connect/stripe/StripeConnect"; export { CreateEventTypePlatformWrapper as CreateEventType } from "./event-types/wrappers/CreateEventTypePlatformWrapper"; diff --git a/packages/platform/atoms/package.json b/packages/platform/atoms/package.json index d432bedc43..c36d480df6 100644 --- a/packages/platform/atoms/package.json +++ b/packages/platform/atoms/package.json @@ -43,7 +43,8 @@ "exports": { ".": { "import": "./dist/cal-atoms.js", - "types": "./dist/index.d.ts" + "types": "./dist/index.d.ts", + "require": "./dist/cal-atoms.umd.cjs" }, "./add-members-switch/AddMembersWithSwitchPlatformWrapper": "./add-members-switch/AddMembersWithSwitchPlatformWrapper.tsx", "./add-members-switch/AddMembersWithSwitchWebWrapper": "./add-members-switch/AddMembersWithSwitchWebWrapper.tsx", @@ -84,5 +85,6 @@ "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "typescript": "^5.0.0" - } + }, + "main": "./dist/cal-atoms.umd.cjs" } diff --git a/packages/platform/examples/base/src/pages/availability.tsx b/packages/platform/examples/base/src/pages/availability.tsx index 068e499de9..5fdb1d0cfc 100644 --- a/packages/platform/examples/base/src/pages/availability.tsx +++ b/packages/platform/examples/base/src/pages/availability.tsx @@ -1,16 +1,49 @@ import { Navbar } from "@/components/Navbar"; import { Inter } from "next/font/google"; +import { useRef, useCallback } from "react"; +import type { AvailabilitySettingsFormRef } from "@calcom/atoms"; import { AvailabilitySettings } from "@calcom/atoms"; const inter = Inter({ subsets: ["latin"] }); export default function Availability(props: { calUsername: string; calEmail: string }) { + const availabilityRef = useRef(null); + + const handleFormStateChange = useCallback((formState: unknown) => { + console.log(formState, "formStateeeeee"); + }, []); + + const handleValidate = async () => { + const result = await availabilityRef.current?.validateForm(); + console.log("Validation result:", result); + }; + + const handleSubmit = () => { + availabilityRef.current?.handleFormSubmit(); + }; + return (
+

Availability Settings

+ +
+ + +
+ { console.log("Updated successfully"); }} diff --git a/packages/platform/examples/base/src/pages/event-types.tsx b/packages/platform/examples/base/src/pages/event-types.tsx index 894364fb6f..4e929443b3 100644 --- a/packages/platform/examples/base/src/pages/event-types.tsx +++ b/packages/platform/examples/base/src/pages/event-types.tsx @@ -2,8 +2,9 @@ import { Navbar } from "@/components/Navbar"; import { Inter } from "next/font/google"; // eslint-disable-next-line @calcom/eslint/deprecated-imports-next-router import { useRouter } from "next/router"; -import { useState } from "react"; +import { useState, useRef, useCallback } from "react"; +import type { EventSettingsFromRef } from "@calcom/atoms"; import { useEventTypes, useTeamEventTypes, @@ -18,6 +19,20 @@ export default function Bookings(props: { calUsername: string; calEmail: string const [eventTypeId, setEventTypeId] = useState(null); const [isTeamEvent, setIsTeamEvent] = useState(false); const router = useRouter(); + const eventTypeRef = useRef(null); + + const handleFormStateChange = useCallback((formState: unknown) => { + console.log(formState, "formStateeeeee"); + }, []); + + const handleValidate = async () => { + const result = await eventTypeRef.current?.validateForm(); + console.log("Validation result:", result); + }; + + const handleSubmit = () => { + eventTypeRef.current?.handleFormSubmit(); + }; const { isLoading: isLoadingEvents, data: eventTypes, refetch } = useEventTypes(props.calUsername); const { data: teams } = useTeams(); const { @@ -739,7 +754,22 @@ export default function Bookings(props: { calUsername: string; calEmail: string setEventTypeId(null); }} onDeleteError={console.error} + onFormStateChange={handleFormStateChange} + ref={eventTypeRef} /> + +
+ + +
)}