Skip to content

Commit ea80e8c

Browse files
fix: enable webhook form submit button when all required fields are filled (calcom#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 <hey@eunjae.dev> * 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 <hey@eunjae.dev> * fix: resolve type error by moving canSubmit computation after changeSecret declaration Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * 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 <hey@eunjae.dev> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent cba0fa8 commit ea80e8c

1 file changed

Lines changed: 26 additions & 8 deletions

File tree

packages/features/webhooks/components/WebhookForm.tsx

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -291,13 +291,20 @@ const WebhookForm = (props: {
291291
},
292292
});
293293

294-
const showTimeSection = formMethods
295-
.watch("eventTriggers")
296-
?.find(
297-
(trigger) =>
298-
trigger === WebhookTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW ||
299-
trigger === WebhookTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW
300-
);
294+
const triggers = formMethods.watch("eventTriggers") || [];
295+
const subscriberUrl = formMethods.watch("subscriberUrl");
296+
const time = formMethods.watch("time");
297+
const timeUnit = formMethods.watch("timeUnit");
298+
299+
const isCreating = !props?.webhook?.id;
300+
const needsTime = triggers.some(
301+
(t) =>
302+
t === WebhookTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW ||
303+
t === WebhookTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW
304+
);
305+
const hasTime = !!time && !!timeUnit;
306+
const hasUrl = !!subscriberUrl;
307+
const showTimeSection = needsTime;
301308

302309
const [useCustomTemplate, setUseCustomTemplate] = useState(
303310
props?.webhook?.payloadTemplate !== undefined && props?.webhook?.payloadTemplate !== null
@@ -329,6 +336,17 @@ const WebhookForm = (props: {
329336
const [changeSecret, setChangeSecret] = useState<boolean>(false);
330337
const hasSecretKey = !!props?.webhook?.secret;
331338

339+
const canSubmit = isCreating
340+
? hasUrl && triggers.length > 0 && (!needsTime || hasTime)
341+
: formMethods.formState.isDirty || changeSecret;
342+
343+
useEffect(() => {
344+
if (isCreating && needsTime && !time && !timeUnit) {
345+
formMethods.setValue("time", 5, { shouldDirty: true });
346+
formMethods.setValue("timeUnit", TimeUnit.MINUTE, { shouldDirty: true });
347+
}
348+
}, [isCreating, needsTime, time, timeUnit, formMethods]);
349+
332350
useEffect(() => {
333351
if (changeSecret) {
334352
formMethods.unregister("secret", { keepDefaultValue: false });
@@ -585,7 +603,7 @@ const WebhookForm = (props: {
585603
<Button
586604
type="submit"
587605
data-testid="create_webhook"
588-
disabled={!formMethods.formState.isDirty && !changeSecret}
606+
disabled={!canSubmit}
589607
loading={formMethods.formState.isSubmitting}>
590608
{props?.webhook?.id ? t("save") : t("create_webhook")}
591609
</Button>

0 commit comments

Comments
 (0)