11'use client'
22
3- import { useEffect , useState } from 'react'
3+ import { useEffect , useRef , useState } from 'react'
44import { Loader2 , MoreVertical , X } from 'lucide-react'
55import {
66 Button ,
@@ -102,6 +102,18 @@ export function DeployModal({
102102 const [ previewDeployedState , setPreviewDeployedState ] = useState < WorkflowState | null > ( null )
103103 const [ currentPage , setCurrentPage ] = useState ( 1 )
104104 const itemsPerPage = 5
105+ const [ editingVersion , setEditingVersion ] = useState < number | null > ( null )
106+ const [ editValue , setEditValue ] = useState ( '' )
107+ const [ isRenaming , setIsRenaming ] = useState ( false )
108+ const [ openDropdown , setOpenDropdown ] = useState < number | null > ( null )
109+ const inputRef = useRef < HTMLInputElement > ( null )
110+
111+ useEffect ( ( ) => {
112+ if ( editingVersion !== null && inputRef . current ) {
113+ inputRef . current . focus ( )
114+ inputRef . current . select ( )
115+ }
116+ } , [ editingVersion ] )
105117
106118 const getInputFormatExample = ( includeStreaming = false ) => {
107119 let inputFormatExample = ''
@@ -419,6 +431,52 @@ export function DeployModal({
419431 }
420432 }
421433
434+ const handleStartRename = ( version : number , currentName : string | null | undefined ) => {
435+ setOpenDropdown ( null ) // Close dropdown first
436+ setEditingVersion ( version )
437+ setEditValue ( currentName || `v${ version } ` )
438+ }
439+
440+ const handleSaveRename = async ( version : number ) => {
441+ if ( ! workflowId || ! editValue . trim ( ) ) {
442+ setEditingVersion ( null )
443+ return
444+ }
445+
446+ const currentVersion = versions . find ( ( v ) => v . version === version )
447+ const currentName = currentVersion ?. name || `v${ version } `
448+
449+ if ( editValue . trim ( ) === currentName ) {
450+ setEditingVersion ( null )
451+ return
452+ }
453+
454+ setIsRenaming ( true )
455+ try {
456+ const res = await fetch ( `/api/workflows/${ workflowId } /deployments/${ version } ` , {
457+ method : 'PATCH' ,
458+ headers : { 'Content-Type' : 'application/json' } ,
459+ body : JSON . stringify ( { name : editValue . trim ( ) } ) ,
460+ } )
461+
462+ if ( res . ok ) {
463+ await fetchVersions ( )
464+ setEditingVersion ( null )
465+ } else {
466+ logger . error ( 'Failed to rename version' )
467+ }
468+ } catch ( error ) {
469+ logger . error ( 'Error renaming version:' , error )
470+ } finally {
471+ setIsRenaming ( false )
472+ }
473+ }
474+
475+ const handleCancelRename = ( ) => {
476+ setEditingVersion ( null )
477+ setEditValue ( '' )
478+ }
479+
422480 const handleUndeploy = async ( ) => {
423481 try {
424482 setIsUndeploying ( true )
@@ -539,7 +597,7 @@ export function DeployModal({
539597 return (
540598 < Dialog open = { open } onOpenChange = { handleCloseModal } >
541599 < DialogContent
542- className = 'flex max-h-[90vh] flex-col gap-0 overflow-hidden p-0 sm:max-w-[600px ]'
600+ className = 'flex max-h-[90vh] flex-col gap-0 overflow-hidden p-0 sm:max-w-[700px ]'
543601 hideCloseButton
544602 >
545603 < DialogHeader className = 'flex-shrink-0 border-b px-6 py-4' >
@@ -650,13 +708,13 @@ export function DeployModal({
650708 < thead className = 'border-b bg-muted/50' >
651709 < tr >
652710 < th className = 'w-10' />
653- < th className = 'px-4 py-2 text-left font-medium text-muted-foreground text-xs' >
711+ < th className = 'w-[200px] whitespace-nowrap px-4 py-2 text-left font-medium text-muted-foreground text-xs' >
654712 Version
655713 </ th >
656- < th className = 'px-4 py-2 text-left font-medium text-muted-foreground text-xs' >
714+ < th className = 'whitespace-nowrap px-4 py-2 text-left font-medium text-muted-foreground text-xs' >
657715 Deployed By
658716 </ th >
659- < th className = 'px-4 py-2 text-left font-medium text-muted-foreground text-xs' >
717+ < th className = 'whitespace-nowrap px-4 py-2 text-left font-medium text-muted-foreground text-xs' >
660718 Created
661719 </ th >
662720 < th className = 'w-10' />
@@ -669,7 +727,11 @@ export function DeployModal({
669727 < tr
670728 key = { v . id }
671729 className = 'cursor-pointer transition-colors hover:bg-muted/30'
672- onClick = { ( ) => openVersionPreview ( v . version ) }
730+ onClick = { ( ) => {
731+ if ( editingVersion !== v . version ) {
732+ openVersionPreview ( v . version )
733+ }
734+ } }
673735 >
674736 < td className = 'px-4 py-2.5' >
675737 < div
@@ -679,22 +741,54 @@ export function DeployModal({
679741 title = { v . isActive ? 'Active' : 'Inactive' }
680742 />
681743 </ td >
682- < td className = 'px-4 py-2.5' >
683- < span className = 'font-medium text-sm' > v{ v . version } </ span >
744+ < td className = 'w-[220px] max-w-[220px] px-4 py-2.5' >
745+ { editingVersion === v . version ? (
746+ < input
747+ ref = { inputRef }
748+ value = { editValue }
749+ onChange = { ( e ) => setEditValue ( e . target . value ) }
750+ onKeyDown = { ( e ) => {
751+ if ( e . key === 'Enter' ) {
752+ e . preventDefault ( )
753+ handleSaveRename ( v . version )
754+ } else if ( e . key === 'Escape' ) {
755+ e . preventDefault ( )
756+ handleCancelRename ( )
757+ }
758+ } }
759+ onBlur = { ( ) => handleSaveRename ( v . version ) }
760+ className = 'w-full border-0 bg-transparent p-0 font-medium text-sm leading-5 outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0'
761+ maxLength = { 100 }
762+ disabled = { isRenaming }
763+ autoComplete = 'off'
764+ autoCorrect = 'off'
765+ autoCapitalize = 'off'
766+ spellCheck = 'false'
767+ />
768+ ) : (
769+ < span className = 'block whitespace-pre-wrap break-words break-all font-medium text-sm leading-5' >
770+ { v . name || `v${ v . version } ` }
771+ </ span >
772+ ) }
684773 </ td >
685- < td className = 'px-4 py-2.5' >
774+ < td className = 'whitespace-nowrap px-4 py-2.5' >
686775 < span className = 'text-muted-foreground text-sm' >
687776 { v . deployedBy || 'Unknown' }
688777 </ span >
689778 </ td >
690- < td className = 'px-4 py-2.5' >
779+ < td className = 'whitespace-nowrap px-4 py-2.5' >
691780 < span className = 'text-muted-foreground text-sm' >
692781 { new Date ( v . createdAt ) . toLocaleDateString ( ) } { ' ' }
693782 { new Date ( v . createdAt ) . toLocaleTimeString ( ) }
694783 </ span >
695784 </ td >
696785 < td className = 'px-4 py-2.5' onClick = { ( e ) => e . stopPropagation ( ) } >
697- < DropdownMenu >
786+ < DropdownMenu
787+ open = { openDropdown === v . version }
788+ onOpenChange = { ( open ) =>
789+ setOpenDropdown ( open ? v . version : null )
790+ }
791+ >
698792 < DropdownMenuTrigger asChild >
699793 < Button
700794 variant = 'ghost'
@@ -705,7 +799,10 @@ export function DeployModal({
705799 < MoreVertical className = 'h-4 w-4' />
706800 </ Button >
707801 </ DropdownMenuTrigger >
708- < DropdownMenuContent align = 'end' >
802+ < DropdownMenuContent
803+ align = 'end'
804+ onCloseAutoFocus = { ( event ) => event . preventDefault ( ) }
805+ >
709806 < DropdownMenuItem
710807 onClick = { ( ) => activateVersion ( v . version ) }
711808 disabled = { v . isActive || activatingVersion === v . version }
@@ -721,6 +818,11 @@ export function DeployModal({
721818 >
722819 Inspect
723820 </ DropdownMenuItem >
821+ < DropdownMenuItem
822+ onClick = { ( ) => handleStartRename ( v . version , v . name ) }
823+ >
824+ Rename
825+ </ DropdownMenuItem >
724826 </ DropdownMenuContent >
725827 </ DropdownMenu >
726828 </ td >
@@ -889,7 +991,9 @@ export function DeployModal({
889991 selectedVersion = { previewVersion }
890992 onActivateVersion = { ( ) => activateVersion ( previewVersion ) }
891993 isActivating = { activatingVersion === previewVersion }
892- selectedVersionLabel = { `v${ previewVersion } ` }
994+ selectedVersionLabel = {
995+ versions . find ( ( v ) => v . version === previewVersion ) ?. name || `v${ previewVersion } `
996+ }
893997 workflowId = { workflowId }
894998 isSelectedVersionActive = { versions . find ( ( v ) => v . version === previewVersion ) ?. isActive }
895999 />
0 commit comments