88 Eye ,
99 EyeOff ,
1010 Pencil ,
11+ Play ,
1112 Plus ,
1213 RefreshCw ,
1314 Search ,
@@ -75,7 +76,7 @@ export function WebhookSettings({ workflowId, open, onOpenChange }: WebhookSetti
7576 const [ showSecret , setShowSecret ] = useState ( false )
7677 const [ editingWebhookId , setEditingWebhookId ] = useState < string | null > ( null )
7778 const [ showForm , setShowForm ] = useState ( false )
78- const [ copySuccess , setCopySuccess ] = useState ( false )
79+ const [ copySuccess , setCopySuccess ] = useState < Record < string , boolean > > ( { } )
7980 const [ searchTerm , setSearchTerm ] = useState ( '' )
8081 const [ isGenerating , setIsGenerating ] = useState ( false )
8182 const [ showDeleteDialog , setShowDeleteDialog ] = useState ( false )
@@ -342,16 +343,19 @@ export function WebhookSettings({ workflowId, open, onOpenChange }: WebhookSetti
342343 setIsGenerating ( false )
343344 }
344345
345- const copyToClipboard = ( text : string ) => {
346+ const copyToClipboard = ( text : string , webhookId : string ) => {
346347 navigator . clipboard . writeText ( text )
347- setCopySuccess ( true )
348+ setCopySuccess ( ( prev ) => ( { ... prev , [ webhookId ] : true } ) )
348349 setTimeout ( ( ) => {
349- setCopySuccess ( false )
350+ setCopySuccess ( ( prev ) => ( { ... prev , [ webhookId ] : false } ) )
350351 } , 2000 )
351352 }
352353
354+ const [ originalWebhook , setOriginalWebhook ] = useState < WebhookConfig | null > ( null )
355+
353356 const startEditWebhook = ( webhook : WebhookConfig ) => {
354357 setEditingWebhookId ( webhook . id )
358+ setOriginalWebhook ( webhook )
355359 setNewWebhook ( {
356360 url : webhook . url ,
357361 secret : '' , // Don't expose the existing secret
@@ -368,6 +372,7 @@ export function WebhookSettings({ workflowId, open, onOpenChange }: WebhookSetti
368372
369373 const cancelEdit = ( ) => {
370374 setEditingWebhookId ( null )
375+ setOriginalWebhook ( null )
371376 setFieldErrors ( { } )
372377 setOperationStatus ( { type : null , message : '' } )
373378 setNewWebhook ( {
@@ -383,6 +388,22 @@ export function WebhookSettings({ workflowId, open, onOpenChange }: WebhookSetti
383388 setShowForm ( false )
384389 }
385390
391+ const hasChanges = ( ) => {
392+ if ( ! originalWebhook ) return false
393+ return (
394+ newWebhook . url !== originalWebhook . url ||
395+ newWebhook . includeFinalOutput !== originalWebhook . includeFinalOutput ||
396+ newWebhook . includeTraceSpans !== originalWebhook . includeTraceSpans ||
397+ newWebhook . includeRateLimits !== ( originalWebhook . includeRateLimits || false ) ||
398+ newWebhook . includeUsageData !== ( originalWebhook . includeUsageData || false ) ||
399+ JSON . stringify ( [ ...newWebhook . levelFilter ] . sort ( ) ) !==
400+ JSON . stringify ( [ ...originalWebhook . levelFilter ] . sort ( ) ) ||
401+ JSON . stringify ( [ ...newWebhook . triggerFilter ] . sort ( ) ) !==
402+ JSON . stringify ( [ ...originalWebhook . triggerFilter ] . sort ( ) ) ||
403+ newWebhook . secret !== ''
404+ )
405+ }
406+
386407 const handleCloseModal = ( ) => {
387408 cancelEdit ( )
388409 setOperationStatus ( { type : null , message : '' } )
@@ -555,20 +576,44 @@ export function WebhookSettings({ workflowId, open, onOpenChange }: WebhookSetti
555576 ) : (
556577 < >
557578 { filteredWebhooks . map ( ( webhook , index ) => (
558- < div key = { webhook . id } className = 'relative flex flex-col gap-2' >
579+ < div key = { webhook . id } className = 'relative mb-4 flex flex-col gap-2' >
559580 < Label className = 'font-normal text-muted-foreground text-xs uppercase' >
560581 Webhook { index + 1 }
561582 </ Label >
562583 < div className = 'flex flex-col gap-2' >
563584 < div className = 'flex items-center justify-between gap-4' >
564585 < div className = 'flex flex-1 items-center gap-3' >
565- < div className = 'flex h-8 items-center rounded-[8px] bg-muted px-3' >
566- < code className = 'font-mono text-foreground text-xs' >
567- { webhook . url . length > 40
568- ? `${ webhook . url . substring ( 0 , 37 ) } ...`
569- : webhook . url }
586+ < div className = 'flex h-8 max-w-[400px] items-center overflow-hidden rounded-[8px] bg-muted px-3' >
587+ < code className = 'scrollbar-hide overflow-x-auto whitespace-nowrap font-mono text-foreground text-xs' >
588+ { webhook . url }
570589 </ code >
571590 </ div >
591+ < Tooltip >
592+ < TooltipTrigger asChild >
593+ < Button
594+ variant = 'ghost'
595+ size = 'icon'
596+ onClick = { ( ) => copyToClipboard ( webhook . url , webhook . id ) }
597+ className = { cn (
598+ 'group relative h-8 w-8 rounded-md border border-border/40 bg-background/80 backdrop-blur-sm' ,
599+ 'text-muted-foreground/70 shadow-sm transition-all duration-200' ,
600+ 'hover:border-border hover:bg-muted/50 hover:text-foreground hover:shadow-md' ,
601+ 'active:scale-95 active:shadow-sm' ,
602+ 'focus-visible:ring-2 focus-visible:ring-muted-foreground/20 focus-visible:ring-offset-1'
603+ ) }
604+ >
605+ { copySuccess [ webhook . id ] ? (
606+ < Check className = 'h-3.5 w-3.5 text-foreground' />
607+ ) : (
608+ < Copy className = 'h-3.5 w-3.5 transition-transform duration-200 group-hover:scale-110' />
609+ ) }
610+ < span className = 'sr-only' > Copy webhook URL</ span >
611+ </ Button >
612+ </ TooltipTrigger >
613+ < TooltipContent side = 'top' align = 'center' >
614+ Copy webhook URL
615+ </ TooltipContent >
616+ </ Tooltip >
572617
573618 { /* Test Status inline for this specific webhook */ }
574619 { testStatus &&
@@ -598,13 +643,7 @@ export function WebhookSettings({ workflowId, open, onOpenChange }: WebhookSetti
598643 'focus-visible:ring-2 focus-visible:ring-muted-foreground/20 focus-visible:ring-offset-1'
599644 ) }
600645 >
601- < RefreshCw
602- className = { cn (
603- 'h-3.5 w-3.5 transition-transform duration-200' ,
604- 'group-hover:scale-110' ,
605- isTesting === webhook . id && 'animate-spin'
606- ) }
607- />
646+ < Play className = 'h-3.5 w-3.5 transition-transform duration-200 group-hover:scale-110' />
608647 < span className = 'sr-only' > Test webhook</ span >
609648 </ Button >
610649 </ TooltipTrigger >
@@ -830,7 +869,7 @@ export function WebhookSettings({ workflowId, open, onOpenChange }: WebhookSetti
830869 type = 'button'
831870 variant = 'ghost'
832871 size = 'sm'
833- onClick = { ( ) => copyToClipboard ( newWebhook . secret ) }
872+ onClick = { ( ) => copyToClipboard ( newWebhook . secret , 'form' ) }
834873 disabled = { ! newWebhook . secret }
835874 className = { cn (
836875 'group h-7 w-7 rounded-md p-0' ,
@@ -841,7 +880,7 @@ export function WebhookSettings({ workflowId, open, onOpenChange }: WebhookSetti
841880 'focus-visible:ring-2 focus-visible:ring-muted-foreground/20 focus-visible:ring-offset-1'
842881 ) }
843882 >
844- { copySuccess ? (
883+ { copySuccess . form ? (
845884 < Check className = 'h-3.5 w-3.5 text-foreground' />
846885 ) : (
847886 < Copy className = 'h-3.5 w-3.5 transition-transform duration-200 group-hover:scale-110' />
@@ -1060,15 +1099,13 @@ export function WebhookSettings({ workflowId, open, onOpenChange }: WebhookSetti
10601099 isCreating ||
10611100 ! newWebhook . url ||
10621101 newWebhook . levelFilter . length === 0 ||
1063- newWebhook . triggerFilter . length === 0
1102+ newWebhook . triggerFilter . length === 0 ||
1103+ ( ! ! editingWebhookId && ! hasChanges ( ) )
10641104 }
10651105 className = 'h-9 rounded-[8px] bg-[var(--brand-primary-hex)] font-[480] text-white shadow-[0_0_0_0_var(--brand-primary-hex)] transition-all duration-200 hover:bg-[var(--brand-primary-hover-hex)] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)] disabled:opacity-50 disabled:hover:shadow-none'
10661106 >
10671107 { isCreating ? (
1068- < >
1069- < RefreshCw className = 'mr-2 h-4 w-4 animate-spin' />
1070- { editingWebhookId ? 'Updating...' : 'Creating...' }
1071- </ >
1108+ < > { editingWebhookId ? 'Updating...' : 'Creating...' } </ >
10721109 ) : (
10731110 < > { editingWebhookId ? 'Update Webhook' : 'Create Webhook' } </ >
10741111 ) }
0 commit comments