From ea80e8c8d4adfec83743ee64787771613665a90e Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Thu, 13 Nov 2025 11:32:17 +0100 Subject: [PATCH] fix: enable webhook form submit button when all required fields are filled (#25109) * fix: enable webhook form submit button when all required fields are filled The submit button was disabled even when all fields were filled because the form relied on isDirty state. Since eventTriggers are pre-populated with default values, the form never became dirty until the user manually changed the triggers. This fix adds validation logic that: - For new webhooks: checks if required fields (URL, triggers) are filled - For editing webhooks: preserves the existing isDirty behavior - Handles the conditional time/timeUnit requirement for no-show triggers Fixes the issue where users had to manually change event triggers to enable the submit button even though all required information was filled. Co-Authored-By: eunjae@cal.com * refactor: extract webhook form validation logic to computed variables Replaced IIFE in disabled prop with clean computed variables: - Extracted all watch() calls to the top of the component - Computed validation logic as clear, named variables - Reused needsTime for showTimeSection to avoid duplicate watch calls - Simplified button disabled prop to just !canSubmit This improves code readability and maintainability while preserving the same validation behavior. Co-Authored-By: eunjae@cal.com * fix: resolve type error by moving canSubmit computation after changeSecret declaration Co-Authored-By: eunjae@cal.com * fix: auto-initialize time/timeUnit for webhooks with no-show triggers When creating a webhook with default event triggers that include no-show triggers (AFTER_HOSTS_CAL_VIDEO_NO_SHOW or AFTER_GUESTS_CAL_VIDEO_NO_SHOW), the time and timeUnit fields are now automatically initialized to default values (5 minutes). This ensures the submit button is enabled when all required fields are filled, fixing E2E test failures in webhook.e2e.ts. Co-Authored-By: eunjae@cal.com --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../webhooks/components/WebhookForm.tsx | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/features/webhooks/components/WebhookForm.tsx b/packages/features/webhooks/components/WebhookForm.tsx index bf10b9cc5a..c4a02e60be 100644 --- a/packages/features/webhooks/components/WebhookForm.tsx +++ b/packages/features/webhooks/components/WebhookForm.tsx @@ -291,13 +291,20 @@ const WebhookForm = (props: { }, }); - const showTimeSection = formMethods - .watch("eventTriggers") - ?.find( - (trigger) => - trigger === WebhookTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW || - trigger === WebhookTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW - ); + const triggers = formMethods.watch("eventTriggers") || []; + const subscriberUrl = formMethods.watch("subscriberUrl"); + const time = formMethods.watch("time"); + const timeUnit = formMethods.watch("timeUnit"); + + const isCreating = !props?.webhook?.id; + const needsTime = triggers.some( + (t) => + t === WebhookTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW || + t === WebhookTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW + ); + const hasTime = !!time && !!timeUnit; + const hasUrl = !!subscriberUrl; + const showTimeSection = needsTime; const [useCustomTemplate, setUseCustomTemplate] = useState( props?.webhook?.payloadTemplate !== undefined && props?.webhook?.payloadTemplate !== null @@ -329,6 +336,17 @@ const WebhookForm = (props: { const [changeSecret, setChangeSecret] = useState(false); const hasSecretKey = !!props?.webhook?.secret; + const canSubmit = isCreating + ? hasUrl && triggers.length > 0 && (!needsTime || hasTime) + : formMethods.formState.isDirty || changeSecret; + + useEffect(() => { + if (isCreating && needsTime && !time && !timeUnit) { + formMethods.setValue("time", 5, { shouldDirty: true }); + formMethods.setValue("timeUnit", TimeUnit.MINUTE, { shouldDirty: true }); + } + }, [isCreating, needsTime, time, timeUnit, formMethods]); + useEffect(() => { if (changeSecret) { formMethods.unregister("secret", { keepDefaultValue: false }); @@ -585,7 +603,7 @@ const WebhookForm = (props: {