Skip to content

Commit c863125

Browse files
authored
feat(workspace): added option to leave workspace (#2854)
1 parent fa63af9 commit c863125

File tree

3 files changed

+71
-2
lines changed

3 files changed

+71
-2
lines changed

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/context-menu/context-menu.tsx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,20 @@ interface ContextMenuProps {
161161
* Set to true when creation is in progress or user lacks permissions
162162
*/
163163
disableCreateFolder?: boolean
164+
/**
165+
* Callback when leave is clicked (for workspaces)
166+
*/
167+
onLeave?: () => void
168+
/**
169+
* Whether to show the leave option (default: false)
170+
* Set to true for workspaces the user can leave
171+
*/
172+
showLeave?: boolean
173+
/**
174+
* Whether the leave option is disabled (default: false)
175+
* Set to true when user cannot leave (e.g., last admin)
176+
*/
177+
disableLeave?: boolean
164178
}
165179

166180
/**
@@ -198,6 +212,9 @@ export function ContextMenu({
198212
disableDelete = false,
199213
disableCreate = false,
200214
disableCreateFolder = false,
215+
onLeave,
216+
showLeave = false,
217+
disableLeave = false,
201218
}: ContextMenuProps) {
202219
const [hexInput, setHexInput] = useState(currentColor || '#ffffff')
203220

@@ -412,8 +429,20 @@ export function ContextMenu({
412429
</PopoverItem>
413430
)}
414431

415-
{/* Destructive action */}
432+
{/* Destructive actions */}
416433
{(hasNavigationSection || hasEditSection || hasCopySection) && <PopoverDivider rootOnly />}
434+
{showLeave && onLeave && (
435+
<PopoverItem
436+
rootOnly
437+
disabled={disableLeave}
438+
onClick={() => {
439+
onLeave()
440+
onClose()
441+
}}
442+
>
443+
Leave
444+
</PopoverItem>
445+
)}
417446
<PopoverItem
418447
rootOnly
419448
disabled={disableDelete}

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,14 @@ interface WorkspaceHeaderProps {
103103
* Whether to show the collapse button
104104
*/
105105
showCollapseButton?: boolean
106+
/**
107+
* Callback to leave the workspace
108+
*/
109+
onLeaveWorkspace?: (workspaceId: string) => Promise<void>
110+
/**
111+
* Current user's session ID for owner check
112+
*/
113+
sessionUserId?: string
106114
}
107115

108116
/**
@@ -128,6 +136,8 @@ export function WorkspaceHeader({
128136
onImportWorkspace,
129137
isImportingWorkspace,
130138
showCollapseButton = true,
139+
onLeaveWorkspace,
140+
sessionUserId,
131141
}: WorkspaceHeaderProps) {
132142
const [isInviteModalOpen, setIsInviteModalOpen] = useState(false)
133143
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false)
@@ -267,6 +277,16 @@ export function WorkspaceHeader({
267277
}
268278
}
269279

280+
/**
281+
* Handles leave action from context menu
282+
*/
283+
const handleLeaveAction = async () => {
284+
if (!capturedWorkspaceRef.current || !onLeaveWorkspace) return
285+
286+
await onLeaveWorkspace(capturedWorkspaceRef.current.id)
287+
setIsWorkspaceMenuOpen(false)
288+
}
289+
270290
/**
271291
* Handle delete workspace
272292
*/
@@ -512,6 +532,8 @@ export function WorkspaceHeader({
512532
const capturedPermissions = capturedWorkspaceRef.current?.permissions
513533
const contextCanEdit = capturedPermissions === 'admin' || capturedPermissions === 'write'
514534
const contextCanAdmin = capturedPermissions === 'admin'
535+
const capturedWorkspace = workspaces.find((w) => w.id === capturedWorkspaceRef.current?.id)
536+
const isOwner = capturedWorkspace && sessionUserId === capturedWorkspace.ownerId
515537

516538
return (
517539
<ContextMenu
@@ -523,10 +545,12 @@ export function WorkspaceHeader({
523545
onDuplicate={handleDuplicateAction}
524546
onExport={handleExportAction}
525547
onDelete={handleDeleteAction}
548+
onLeave={handleLeaveAction}
526549
showRename={true}
527550
showDuplicate={true}
528551
showExport={true}
529-
disableRename={!contextCanEdit}
552+
showLeave={!isOwner && !!onLeaveWorkspace}
553+
disableRename={!contextCanAdmin}
530554
disableDuplicate={!contextCanEdit}
531555
disableExport={!contextCanAdmin}
532556
disableDelete={!contextCanAdmin}

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ export function Sidebar() {
157157
isCreatingWorkspace,
158158
updateWorkspaceName,
159159
confirmDeleteWorkspace,
160+
handleLeaveWorkspace,
160161
} = useWorkspaceManagement({
161162
workspaceId,
162163
sessionUserId: sessionData?.user?.id,
@@ -378,6 +379,17 @@ export function Sidebar() {
378379
[workspaces, confirmDeleteWorkspace]
379380
)
380381

382+
/** Leaves a workspace */
383+
const handleLeaveWorkspaceWrapper = useCallback(
384+
async (workspaceIdToLeave: string) => {
385+
const workspaceToLeave = workspaces.find((w) => w.id === workspaceIdToLeave)
386+
if (workspaceToLeave) {
387+
await handleLeaveWorkspace(workspaceToLeave)
388+
}
389+
},
390+
[workspaces, handleLeaveWorkspace]
391+
)
392+
381393
/** Duplicates a workspace */
382394
const handleDuplicateWorkspace = useCallback(
383395
async (_workspaceIdToDuplicate: string, workspaceName: string) => {
@@ -509,6 +521,8 @@ export function Sidebar() {
509521
onImportWorkspace={handleImportWorkspace}
510522
isImportingWorkspace={isImportingWorkspace}
511523
showCollapseButton={isOnWorkflowPage}
524+
onLeaveWorkspace={handleLeaveWorkspaceWrapper}
525+
sessionUserId={sessionData?.user?.id}
512526
/>
513527
</div>
514528
) : (
@@ -542,6 +556,8 @@ export function Sidebar() {
542556
onImportWorkspace={handleImportWorkspace}
543557
isImportingWorkspace={isImportingWorkspace}
544558
showCollapseButton={isOnWorkflowPage}
559+
onLeaveWorkspace={handleLeaveWorkspaceWrapper}
560+
sessionUserId={sessionData?.user?.id}
545561
/>
546562
</div>
547563

0 commit comments

Comments
 (0)