fix: auto close modal when deleting local proj workspace

This commit is contained in:
George He committed 2026-10-23 16:09:42 +08:00
1 parent 25858ff7c8
commit 46d949fe3d
3 files changed
+36 -26

No files matched your search

@@ -1,6 +1,5 @@
import type { Project, Workspace } from 'insomnia-data';
import { models } from 'insomnia-data';
import { useEffect, useRef } from 'react';
import { Button, Dialog, Heading, Label, Modal, ModalOverlay, Radio, RadioGroup } from 'react-aria-components';
import { href } from 'react-router';
@@ -13,38 +12,25 @@ interface Props {
workspace: Workspace;
project: Project;
organizationId: string;
// Driven by the host: it closes once the workspace is gone, so the overlay can play its exit animation
isOpen: boolean;
onClose: () => void;
onDeleted?: () => void;
}
export const WorkspaceDeleteModal = ({ workspace, project, organizationId, onClose, onDeleted }: Props) => {
export const WorkspaceDeleteModal = ({ workspace, project, organizationId, isOpen, onClose }: Props) => {
const deleteWorkspaceFetcher = useWorkspaceDeleteActionFetcher();
const workspaceId = workspace._id;
const workspaceName = workspace.name;
// Close once the deletion succeeds (the action redirects, so no error data is returned)
const wasSubmitting = useRef(false);
useEffect(() => {
if (deleteWorkspaceFetcher.state !== 'idle') {
wasSubmitting.current = true;
} else if (wasSubmitting.current) {
wasSubmitting.current = false;
if (!deleteWorkspaceFetcher.data?.error) {
onDeleted?.();
onClose();
}
}
}, [deleteWorkspaceFetcher.state, deleteWorkspaceFetcher.data, onClose, onDeleted]);
return (
<ModalOverlay
isOpen
onOpenChange={() => onClose()}
isOpen={isOpen}
onOpenChange={open => !open && onClose()}
isDismissable
className="fixed top-0 left-0 z-10 flex h-(--visual-viewport-height) w-full items-center justify-center bg-black/30"
className="fixed top-0 left-0 z-10 flex h-(--visual-viewport-height) w-full items-center justify-center bg-black/30 transition-opacity duration-150 data-exiting:opacity-0"
>
<Modal
onOpenChange={() => onClose()}
onOpenChange={open => !open && onClose()}
className="max-h-full w-full max-w-2xl rounded-md border border-solid border-(--hl-sm) bg-(--color-bg) p-(--padding-lg) text-(--color-font)"
>
<Dialog className="outline-hidden">
@@ -660,6 +660,7 @@ const ProjectNavigationSidebarInner = (
const [shortcutTargetItemId, setShortcutTargetItemId] = useState<string | null>(null);
const showRenameModal = useRenameModal();
const { showDeleteModal, deleteModal } = useDeleteModal({
flatItems,
onDeleteProject: projectId => deleteProjectFetcher.submit({ organizationId, projectId }),
// Leave focus mode when its collection is deleted, otherwise the view is left empty
onWorkspaceDeleted: workspaceId => {
@@ -1,5 +1,5 @@
import { models, services } from 'insomnia-data';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useRequestDeleteActionFetcher } from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.delete';
import { useRequestGroupDeleteActionFetcher } from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request-group.delete';
@@ -8,21 +8,42 @@ import { AskModal } from '~/ui/components/modals/ask-modal';
import { WorkspaceDeleteModal } from '~/ui/components/modals/workspace-delete-modal';
import { getRequestNameOrFallback } from './request-node';
import type { CollectionChildFlatItem, PinnedRequestFlatItem, ProjectFlatItem, WorkspaceFlatItem } from './types';
import type {
CollectionChildFlatItem,
FlatItem,
PinnedRequestFlatItem,
ProjectFlatItem,
WorkspaceFlatItem,
} from './types';
export type DeleteTargetItem = ProjectFlatItem | WorkspaceFlatItem | CollectionChildFlatItem | PinnedRequestFlatItem;
interface Options {
// Unfiltered sidebar items, used to notice when the workspace being deleted is gone
flatItems: FlatItem[];
onDeleteProject: (projectId: string) => void;
onWorkspaceDeleted?: (workspaceId: string) => void;
}
// Returns a function to open the delete confirmation for projects, workspaces, request groups, and requests,
// plus the element that hosts the workspace confirmation, which must be rendered once by the caller.
export const useDeleteModal = ({ onDeleteProject, onWorkspaceDeleted }: Options) => {
export const useDeleteModal = ({ flatItems, onDeleteProject, onWorkspaceDeleted }: Options) => {
const deleteRequestFetcher = useRequestDeleteActionFetcher();
const deleteRequestGroupFetcher = useRequestGroupDeleteActionFetcher();
// Kept after closing so the modal can play its exit animation
const [workspaceToDelete, setWorkspaceToDelete] = useState<WorkspaceFlatItem | null>(null);
const [isWorkspaceModalOpen, setIsWorkspaceModalOpen] = useState(false);
// The workspace disappearing from the sidebar is the signal that the deletion succeeded.
// On failure (e.g. a remote error) it stays, so the modal remains open to show the error.
const isWorkspaceGone =
!!workspaceToDelete && !flatItems.some(i => i.kind === 'workspace' && i.doc._id === workspaceToDelete.doc._id);
useEffect(() => {
if (isWorkspaceModalOpen && isWorkspaceGone) {
setIsWorkspaceModalOpen(false);
onWorkspaceDeleted?.(workspaceToDelete.doc._id);
}
}, [isWorkspaceModalOpen, isWorkspaceGone, workspaceToDelete, onWorkspaceDeleted]);
const showDeleteModal = (item: DeleteTargetItem) => {
if (item.kind === 'project') {
@@ -65,6 +86,7 @@ export const useDeleteModal = ({ onDeleteProject, onWorkspaceDeleted }: Options)
return;
}
setWorkspaceToDelete(item);
setIsWorkspaceModalOpen(true);
return;
}
@@ -113,11 +135,12 @@ export const useDeleteModal = ({ onDeleteProject, onWorkspaceDeleted }: Options)
const deleteModal = workspaceToDelete && (
<WorkspaceDeleteModal
key={workspaceToDelete.doc._id}
isOpen={isWorkspaceModalOpen}
workspace={workspaceToDelete.doc}
project={workspaceToDelete.project}
organizationId={workspaceToDelete.organizationId}
onClose={() => setWorkspaceToDelete(null)}
onDeleted={() => onWorkspaceDeleted?.(workspaceToDelete.doc._id)}
onClose={() => setIsWorkspaceModalOpen(false)}
/>
);