diff --git a/packages/bruno-app/src/components/Modal/index.js b/packages/bruno-app/src/components/Modal/index.js
index ca1513e48..36ca7a4fc 100644
--- a/packages/bruno-app/src/components/Modal/index.js
+++ b/packages/bruno-app/src/components/Modal/index.js
@@ -96,9 +96,8 @@ const Modal = ({
return closeModal({ type: 'esc' });
}
case ENTER_KEY_CODE: {
- // Skip if a submit button is focused - let native button click handle it to avoid double-fire
const isSubmitButton = event.target?.type === 'submit';
- if (!shiftKey && !ctrlKey && !altKey && !metaKey && handleConfirm && !isSubmitButton) {
+ if (!shiftKey && !ctrlKey && !altKey && !metaKey && handleConfirm && !isSubmitButton && !confirmDisabled) {
return handleConfirm();
}
}
@@ -117,7 +116,7 @@ const Modal = ({
return () => {
document.removeEventListener('keydown', handleKeydown);
};
- }, [disableEscapeKey, document, handleConfirm]);
+ }, [disableEscapeKey, document, handleConfirm, confirmDisabled]);
let classes = 'bruno-modal';
if (isClosing) {
diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/DeleteCollection/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/DeleteCollection/StyledWrapper.js
new file mode 100644
index 000000000..6944fd9a2
--- /dev/null
+++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/DeleteCollection/StyledWrapper.js
@@ -0,0 +1,83 @@
+import styled from 'styled-components';
+import { rgba } from 'polished';
+
+const StyledWrapper = styled.div`
+ .modal-description {
+ color: ${(props) => props.theme.text};
+ margin-bottom: 12px;
+
+ strong {
+ font-weight: 600;
+ }
+ }
+
+ .collection-info-card {
+ background-color: ${(props) => props.theme.modal.title.bg};
+ border-radius: 4px;
+ padding: 12px;
+ margin-bottom: 12px;
+ }
+
+ .collection-name {
+ font-weight: 500;
+ color: ${(props) => props.theme.text};
+ margin-bottom: 4px;
+ }
+
+ .collection-path {
+ font-size: ${(props) => props.theme.font.size.sm};
+ color: ${(props) => props.theme.colors.text.muted};
+ word-break: break-all;
+ }
+
+ .warning-text {
+ font-size: ${(props) => props.theme.font.size.sm};
+ color: ${(props) => props.theme.colors.text.danger};
+ margin-bottom: 16px;
+ }
+
+ .delete-confirmation {
+ padding-top: 16px;
+ border-top: 1px solid ${(props) => props.theme.border.border0};
+
+ label {
+ display: block;
+ font-size: ${(props) => props.theme.font.size.sm};
+ color: ${(props) => props.theme.text};
+ margin-bottom: 8px;
+ }
+
+ .delete-keyword {
+ font-weight: 600;
+ color: ${(props) => props.theme.colors.text.danger};
+ font-family: monospace;
+ background-color: ${(props) => rgba(props.theme.colors.text.danger, 0.1)};
+ padding: 2px 6px;
+ border-radius: 4px;
+ }
+
+ input {
+ width: 100%;
+ padding: 8px 12px;
+ font-size: ${(props) => props.theme.font.size.sm};
+ border: 1px solid ${(props) => props.theme.input.border};
+ border-radius: 6px;
+ background-color: ${(props) => props.theme.input.bg};
+ color: ${(props) => props.theme.text};
+ outline: none;
+ transition: border-color 0.15s ease;
+
+ &::placeholder {
+ color: ${(props) => props.theme.colors.text.muted};
+ opacity: 0.6;
+ }
+
+ &:focus {
+ border-color: ${(props) => props.theme.colors.text.danger};
+ box-shadow: 0 0 0 2px ${(props) => rgba(props.theme.colors.text.danger, 0.15)};
+ }
+ }
+ }
+`;
+
+export default StyledWrapper;
diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/DeleteCollection/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/DeleteCollection/index.js
new file mode 100644
index 000000000..8e40e439a
--- /dev/null
+++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/DeleteCollection/index.js
@@ -0,0 +1,88 @@
+import React, { useState } from 'react';
+import toast from 'react-hot-toast';
+import Modal from 'components/Modal';
+import { useDispatch, useSelector } from 'react-redux';
+import { IconAlertTriangle } from '@tabler/icons';
+import { removeCollectionFromWorkspaceAction } from 'providers/ReduxStore/slices/workspaces/actions';
+import { findCollectionByUid } from 'utils/collections/index';
+import StyledWrapper from './StyledWrapper';
+
+const DeleteCollection = ({ onClose, collectionUid, workspaceUid }) => {
+ const dispatch = useDispatch();
+ const [confirmText, setConfirmText] = useState('');
+ const collection = useSelector((state) => findCollectionByUid(state.collections.collections, collectionUid));
+ const workspace = useSelector((state) => state.workspaces.workspaces.find((w) => w.uid === workspaceUid));
+
+ const isConfirmed = confirmText.toLowerCase() === 'delete';
+
+ const onConfirm = async () => {
+ if (!collection || !workspace) {
+ toast.error('Collection or workspace not found');
+ onClose();
+ return;
+ }
+
+ try {
+ await dispatch(removeCollectionFromWorkspaceAction(workspace.uid, collection.pathname, { deleteFiles: true }));
+ toast.success(`Deleted "${collection.name}" collection`);
+ onClose();
+ } catch (error) {
+ console.error('Error deleting collection:', error);
+ toast.error(error.message || 'An error occurred while deleting the collection');
+ }
+ };
+
+ if (!collection) {
+ return null;
+ }
+
+ const customHeader = (
+
+
+ Delete Collection
+
+ );
+
+ return (
+
+
+
+ Are you sure you want to permanently delete "{collection.name}" ?
+
+
+
{collection.name}
+
{collection.pathname}
+
+
+ This action cannot be undone. The collection files will be permanently deleted from disk.
+
+
+
+ Type delete to confirm
+
+ setConfirmText(e.target.value)}
+ placeholder="delete"
+ autoComplete="off"
+ autoFocus
+ />
+
+
+
+ );
+};
+
+export default DeleteCollection;
diff --git a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceOverview/CollectionsList/index.js b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceOverview/CollectionsList/index.js
index a7481a220..421eb5803 100644
--- a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceOverview/CollectionsList/index.js
+++ b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceOverview/CollectionsList/index.js
@@ -1,13 +1,13 @@
import React, { useState, useMemo, useRef } from 'react';
import { useSelector, useDispatch } from 'react-redux';
-import { IconBox, IconTrash, IconEdit, IconShare, IconDots } from '@tabler/icons';
-import { removeCollectionFromWorkspaceAction } from 'providers/ReduxStore/slices/workspaces/actions';
+import { IconBox, IconTrash, IconEdit, IconShare, IconDots, IconX } from '@tabler/icons';
import { addTab } from 'providers/ReduxStore/slices/tabs';
import { mountCollection } from 'providers/ReduxStore/slices/collections/actions';
import { normalizePath } from 'utils/common/path';
import toast from 'react-hot-toast';
-import Modal from 'components/Modal';
import RenameCollection from 'components/Sidebar/Collections/Collection/RenameCollection';
+import RemoveCollection from 'components/Sidebar/Collections/Collection/RemoveCollection';
+import DeleteCollection from 'components/Sidebar/Collections/Collection/DeleteCollection';
import ShareCollection from 'components/ShareCollection';
import Dropdown from 'components/Dropdown';
import StyledWrapper from './StyledWrapper';
@@ -17,8 +17,9 @@ const CollectionsList = ({ workspace }) => {
const { collections } = useSelector((state) => state.collections);
const dropdownRefs = useRef({});
- const [collectionToRemove, setCollectionToRemove] = useState(null);
const [renameCollectionModalOpen, setRenameCollectionModalOpen] = useState(false);
+ const [removeCollectionModalOpen, setRemoveCollectionModalOpen] = useState(false);
+ const [deleteCollectionModalOpen, setDeleteCollectionModalOpen] = useState(false);
const [shareCollectionModalOpen, setShareCollectionModalOpen] = useState(false);
const [selectedCollectionUid, setSelectedCollectionUid] = useState(null);
@@ -65,35 +66,6 @@ const CollectionsList = ({ workspace }) => {
});
}, [workspace.collections, collections]);
- const isInternalCollection = (collection) => {
- if (!workspace.pathname || !collection.pathname) return false;
- const workspaceCollectionsFolder = normalizePath(`${workspace.pathname}/collections`);
- const collectionPath = normalizePath(collection.pathname);
- return collectionPath.startsWith(workspaceCollectionsFolder);
- };
-
- const getCollectionWorkspaceInfo = (collection) => {
- if (Object.prototype.hasOwnProperty.call(collection, 'isGitBacked')) {
- return {
- isGitBacked: collection.isGitBacked,
- gitRemoteUrl: collection.gitRemoteUrl,
- isLoaded: collection.isLoaded !== false,
- isInternal: isInternalCollection(collection)
- };
- }
-
- const workspaceCollection = workspace.collections?.find(
- (wc) => normalizePath(collection.pathname) === normalizePath(wc.path)
- );
-
- return {
- isGitBacked: !!workspaceCollection?.remote,
- gitRemoteUrl: workspaceCollection?.remote,
- isLoaded: true,
- isInternal: isInternalCollection(collection)
- };
- };
-
const handleOpenCollectionClick = (collection, event) => {
if (event.target.closest('.collection-menu')) {
return;
@@ -156,59 +128,22 @@ const CollectionsList = ({ workspace }) => {
const handleRemoveCollection = (collection) => {
dropdownRefs.current[collection.uid]?.hide();
- setCollectionToRemove(collection);
- };
-
- const confirmRemoveCollection = async () => {
- if (!collectionToRemove) return;
-
- try {
- const collectionInfo = getCollectionWorkspaceInfo(collectionToRemove);
- const isDelete = collectionInfo.isInternal && !collectionInfo.isGitBacked;
-
- await dispatch(removeCollectionFromWorkspaceAction(workspace.uid, collectionToRemove.pathname));
-
- if (isDelete) {
- toast.success(`Deleted "${collectionToRemove.name}" collection`);
- } else {
- toast.success(`Removed "${collectionToRemove.name}" from workspace`);
- }
-
- setCollectionToRemove(null);
- } catch (error) {
- console.error('Error removing collection:', error);
- toast.error(error.message || 'Failed to remove collection from workspace');
+ if (collection.isLoaded === false) {
+ toast.error('Cannot remove collections that are not loaded');
+ return;
}
+ setSelectedCollectionUid(collection.uid);
+ setRemoveCollectionModalOpen(true);
};
- const renderRemoveModal = () => {
- if (!collectionToRemove) return null;
-
- const collectionInfo = getCollectionWorkspaceInfo(collectionToRemove);
- const isDelete = collectionInfo.isInternal && !collectionInfo.isGitBacked;
-
- return (
- setCollectionToRemove(null)}
- handleConfirm={confirmRemoveCollection}
- confirmText={isDelete ? 'Delete' : 'Remove'}
- cancelText="Cancel"
- confirmButtonColor={isDelete ? 'warning' : 'primary'}
- style="new"
- >
-
- Are you sure you want to {isDelete ? 'delete' : 'remove'}{' '}
- "{collectionToRemove.name}" ?
-
-
- {isDelete
- ? 'This will permanently delete the collection files from the workspace collections folder.'
- : 'This will remove the collection from the workspace. The collection files will not be deleted.'}
-
-
- );
+ const handleDeleteCollection = (collection) => {
+ dropdownRefs.current[collection.uid]?.hide();
+ if (collection.isLoaded === false) {
+ toast.error('Cannot delete collections that are not loaded');
+ return;
+ }
+ setSelectedCollectionUid(collection.uid);
+ setDeleteCollectionModalOpen(true);
};
return (
@@ -223,6 +158,27 @@ const CollectionsList = ({ workspace }) => {
/>
)}
+ {removeCollectionModalOpen && selectedCollectionUid && (
+ {
+ setRemoveCollectionModalOpen(false);
+ setSelectedCollectionUid(null);
+ }}
+ />
+ )}
+
+ {deleteCollectionModalOpen && selectedCollectionUid && (
+ {
+ setDeleteCollectionModalOpen(false);
+ setSelectedCollectionUid(null);
+ }}
+ />
+ )}
+
{shareCollectionModalOpen && selectedCollectionUid && (
{
/>
)}
- {renderRemoveModal()}
-
{workspaceCollections.length === 0 ? (
@@ -289,15 +243,25 @@ const CollectionsList = ({ workspace }) => {
Share
{
e.stopPropagation();
handleRemoveCollection(collection);
}}
>
-
+
Remove
+
{
+ e.stopPropagation();
+ handleDeleteCollection(collection);
+ }}
+ >
+
+ Delete
+
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js
index e56051dd0..f1f35298a 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js
@@ -122,9 +122,10 @@ export const openWorkspaceDialog = () => {
};
};
-export const removeCollectionFromWorkspaceAction = (workspaceUid, collectionPath) => {
+export const removeCollectionFromWorkspaceAction = (workspaceUid, collectionPath, options = {}) => {
return async (dispatch, getState) => {
try {
+ const { deleteFiles = false } = options;
const workspacesState = getState().workspaces;
const collectionsState = getState().collections;
const workspace = workspacesState.workspaces.find((w) => w.uid === workspaceUid);
@@ -142,7 +143,8 @@ export const removeCollectionFromWorkspaceAction = (workspaceUid, collectionPath
await ipcRenderer.invoke('renderer:remove-collection-from-workspace',
workspaceUid,
workspace.pathname,
- collectionPath);
+ collectionPath,
+ { deleteFiles });
if (collection) {
const workspaceCollection = workspace.collections?.find(
diff --git a/packages/bruno-electron/src/ipc/workspace.js b/packages/bruno-electron/src/ipc/workspace.js
index 21badfcc8..7926c69ef 100644
--- a/packages/bruno-electron/src/ipc/workspace.js
+++ b/packages/bruno-electron/src/ipc/workspace.js
@@ -552,11 +552,12 @@ const registerWorkspaceIpc = (mainWindow, workspaceWatcher) => {
}
});
- ipcMain.handle('renderer:remove-collection-from-workspace', async (event, workspaceUid, workspacePath, collectionPath) => {
+ ipcMain.handle('renderer:remove-collection-from-workspace', async (event, workspaceUid, workspacePath, collectionPath, options = {}) => {
try {
+ const { deleteFiles = false } = options;
const result = await removeCollectionFromWorkspace(workspacePath, collectionPath);
- if (result.shouldDeleteFiles && result.removedCollection && fs.existsSync(collectionPath)) {
+ if (deleteFiles && result.removedCollection && fs.existsSync(collectionPath)) {
await fsExtra.remove(collectionPath);
}
diff --git a/packages/bruno-electron/src/utils/workspace-config.js b/packages/bruno-electron/src/utils/workspace-config.js
index 002942ae8..ede88d43a 100644
--- a/packages/bruno-electron/src/utils/workspace-config.js
+++ b/packages/bruno-electron/src/utils/workspace-config.js
@@ -358,7 +358,6 @@ const removeCollectionFromWorkspace = async (workspacePath, collectionPath) => {
const config = readWorkspaceConfig(workspacePath);
let removedCollection = null;
- let shouldDeleteFiles = false;
config.collections = (config.collections || []).filter((c) => {
const collectionPathFromYml = c.path;
@@ -373,12 +372,6 @@ const removeCollectionFromWorkspace = async (workspacePath, collectionPath) => {
if (path.normalize(absoluteCollectionPath) === path.normalize(collectionPath)) {
removedCollection = c;
-
- const hasRemote = c.remote;
- const isExternalPath = path.isAbsolute(collectionPathFromYml);
-
- shouldDeleteFiles = !hasRemote && !isExternalPath;
-
return false;
}
@@ -390,7 +383,6 @@ const removeCollectionFromWorkspace = async (workspacePath, collectionPath) => {
return {
removedCollection,
- shouldDeleteFiles,
updatedConfig: config
};
});