Skip to content

Commit f04b22b

Browse files
committed
feat: sidebar context menu, delete workflow hook
1 parent 47edaf5 commit f04b22b

File tree

11 files changed

+828
-114
lines changed

11 files changed

+828
-114
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/panel-new.tsx

Lines changed: 9 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
} from '@/components/emcn'
2626
import { createLogger } from '@/lib/logs/console/logger'
2727
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
28+
import { useDeleteWorkflow } from '@/app/workspace/[workspaceId]/w/hooks'
2829
import { usePanelStore } from '@/stores/panel-new/store'
2930
import type { PanelTab } from '@/stores/panel-new/types'
3031
import { useWorkflowJsonStore } from '@/stores/workflows/json/store'
@@ -69,7 +70,6 @@ export function Panel() {
6970
const [isExporting, setIsExporting] = useState(false)
7071
const [isDuplicating, setIsDuplicating] = useState(false)
7172
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false)
72-
const [isDeleting, setIsDeleting] = useState(false)
7373

7474
// Hooks
7575
const userPermissions = useUserPermissionsContext()
@@ -82,6 +82,14 @@ export function Panel() {
8282
const { getJson } = useWorkflowJsonStore()
8383
const { blocks } = useWorkflowStore()
8484

85+
// Delete workflow hook
86+
const { isDeleting, handleDeleteWorkflow } = useDeleteWorkflow({
87+
workspaceId,
88+
workflowId: activeWorkflowId || '',
89+
isActive: true,
90+
onSuccess: () => setIsDeleteModalOpen(false),
91+
})
92+
8593
// Usage limits hook
8694
const { usageExceeded } = useUsageLimits({
8795
context: 'user',
@@ -215,49 +223,6 @@ export function Panel() {
215223
workspaceId,
216224
])
217225

218-
/**
219-
* Handles deleting the current workflow after confirmation
220-
*/
221-
const handleDeleteWorkflow = useCallback(async () => {
222-
if (!activeWorkflowId || !userPermissions.canEdit || isDeleting) {
223-
return
224-
}
225-
226-
setIsDeleting(true)
227-
try {
228-
// Find next workflow to navigate to
229-
const sidebarWorkflows = Object.values(workflows).filter((w) => w.workspaceId === workspaceId)
230-
const currentIndex = sidebarWorkflows.findIndex((w) => w.id === activeWorkflowId)
231-
232-
let nextWorkflowId: string | null = null
233-
if (sidebarWorkflows.length > 1) {
234-
if (currentIndex < sidebarWorkflows.length - 1) {
235-
nextWorkflowId = sidebarWorkflows[currentIndex + 1].id
236-
} else if (currentIndex > 0) {
237-
nextWorkflowId = sidebarWorkflows[currentIndex - 1].id
238-
}
239-
}
240-
241-
// Navigate first
242-
if (nextWorkflowId) {
243-
router.push(`/workspace/${workspaceId}/w/${nextWorkflowId}`)
244-
} else {
245-
router.push(`/workspace/${workspaceId}`)
246-
}
247-
248-
// Then delete
249-
const { removeWorkflow: registryRemoveWorkflow } = useWorkflowRegistry.getState()
250-
await registryRemoveWorkflow(activeWorkflowId)
251-
252-
setIsDeleteModalOpen(false)
253-
logger.info('Workflow deleted successfully')
254-
} catch (error) {
255-
logger.error('Error deleting workflow:', error)
256-
} finally {
257-
setIsDeleting(false)
258-
}
259-
}, [activeWorkflowId, userPermissions.canEdit, isDeleting, workflows, workspaceId, router])
260-
261226
// Compute run button state
262227
const canRun = userPermissions.canRead // Running only requires read permissions
263228
const isLoadingPermissions = userPermissions.isLoading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
'use client'
2+
3+
import { Pencil } from 'lucide-react'
4+
import { Popover, PopoverAnchor, PopoverContent, PopoverItem } from '@/components/emcn'
5+
import { Trash } from '@/components/emcn/icons'
6+
7+
interface ContextMenuProps {
8+
/**
9+
* Whether the context menu is open
10+
*/
11+
isOpen: boolean
12+
/**
13+
* Position of the context menu
14+
*/
15+
position: { x: number; y: number }
16+
/**
17+
* Ref for the menu element
18+
*/
19+
menuRef: React.RefObject<HTMLDivElement | null>
20+
/**
21+
* Callback when menu should close
22+
*/
23+
onClose: () => void
24+
/**
25+
* Callback when rename is clicked
26+
*/
27+
onRename: () => void
28+
/**
29+
* Callback when delete is clicked
30+
*/
31+
onDelete: () => void
32+
}
33+
34+
/**
35+
* Reusable context menu component for workflow and folder items.
36+
* Displays rename and delete options in a popover at the right-click position.
37+
*
38+
* @param props - Component props
39+
* @returns Context menu popover
40+
*/
41+
export function ContextMenu({
42+
isOpen,
43+
position,
44+
menuRef,
45+
onClose,
46+
onRename,
47+
onDelete,
48+
}: ContextMenuProps) {
49+
return (
50+
<Popover open={isOpen} onOpenChange={onClose}>
51+
<PopoverAnchor
52+
style={{
53+
position: 'fixed',
54+
left: `${position.x}px`,
55+
top: `${position.y}px`,
56+
width: '1px',
57+
height: '1px',
58+
}}
59+
/>
60+
<PopoverContent ref={menuRef} align='start' side='bottom' sideOffset={4}>
61+
<PopoverItem
62+
onClick={() => {
63+
onRename()
64+
onClose()
65+
}}
66+
>
67+
<Pencil className='h-3 w-3' />
68+
<span>Rename</span>
69+
</PopoverItem>
70+
<PopoverItem
71+
onClick={() => {
72+
onDelete()
73+
onClose()
74+
}}
75+
>
76+
<Trash className='h-3 w-3' />
77+
<span>Delete</span>
78+
</PopoverItem>
79+
</PopoverContent>
80+
</Popover>
81+
)
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
'use client'
2+
3+
import {
4+
Modal,
5+
ModalContent,
6+
ModalDescription,
7+
ModalFooter,
8+
ModalHeader,
9+
ModalTitle,
10+
} from '@/components/emcn'
11+
import { Button } from '@/components/ui/button'
12+
13+
interface DeleteModalProps {
14+
/**
15+
* Whether the modal is open
16+
*/
17+
isOpen: boolean
18+
/**
19+
* Callback when modal should close
20+
*/
21+
onClose: () => void
22+
/**
23+
* Callback when delete is confirmed
24+
*/
25+
onConfirm: () => void
26+
/**
27+
* Whether the delete operation is in progress
28+
*/
29+
isDeleting: boolean
30+
/**
31+
* Type of item being deleted
32+
*/
33+
itemType: 'workflow' | 'folder'
34+
/**
35+
* Name of the item being deleted (optional, for display)
36+
*/
37+
itemName?: string
38+
}
39+
40+
/**
41+
* Reusable delete confirmation modal for workflow and folder items.
42+
* Displays a warning message and confirmation buttons.
43+
*
44+
* @param props - Component props
45+
* @returns Delete confirmation modal
46+
*/
47+
export function DeleteModal({
48+
isOpen,
49+
onClose,
50+
onConfirm,
51+
isDeleting,
52+
itemType,
53+
itemName,
54+
}: DeleteModalProps) {
55+
const title = itemType === 'workflow' ? 'Delete workflow?' : 'Delete folder?'
56+
57+
const description =
58+
itemType === 'workflow'
59+
? 'Deleting this workflow will permanently remove all associated blocks, executions, and configuration.'
60+
: 'Deleting this folder will permanently remove all associated workflows, logs, and knowledge bases.'
61+
62+
return (
63+
<Modal open={isOpen} onOpenChange={onClose}>
64+
<ModalContent>
65+
<ModalHeader>
66+
<ModalTitle>{title}</ModalTitle>
67+
<ModalDescription>
68+
{description}{' '}
69+
<span className='text-[#EF4444] dark:text-[#EF4444]'>
70+
This action cannot be undone.
71+
</span>
72+
</ModalDescription>
73+
</ModalHeader>
74+
<ModalFooter>
75+
<Button
76+
className='h-[32px] px-[12px]'
77+
variant='outline'
78+
onClick={onClose}
79+
disabled={isDeleting}
80+
>
81+
Cancel
82+
</Button>
83+
<Button
84+
className='h-[32px] bg-[#EF4444] px-[12px] text-[#FFFFFF] hover:bg-[#EF4444] hover:text-[#FFFFFF] dark:bg-[#EF4444] dark:text-[#FFFFFF] hover:dark:bg-[#EF4444] dark:hover:text-[#FFFFFF]'
85+
onClick={onConfirm}
86+
disabled={isDeleting}
87+
>
88+
{isDeleting ? 'Deleting...' : 'Delete'}
89+
</Button>
90+
</ModalFooter>
91+
</ModalContent>
92+
</Modal>
93+
)
94+
}

0 commit comments

Comments
 (0)