diff --git a/packages/bruno-app/src/components/RequestTabs/CollectionHeader/StyledWrapper.js b/packages/bruno-app/src/components/RequestTabs/CollectionHeader/StyledWrapper.js
index 31eb50f34..54d4efceb 100644
--- a/packages/bruno-app/src/components/RequestTabs/CollectionHeader/StyledWrapper.js
+++ b/packages/bruno-app/src/components/RequestTabs/CollectionHeader/StyledWrapper.js
@@ -17,8 +17,7 @@ const StyledWrapper = styled.div`
background: transparent;
color: ${(props) => props.theme.text};
cursor: pointer;
- font-size: 15px;
- font-weight: 600;
+ font-weight: 500;
transition: background-color 0.15s ease;
&:hover {
@@ -30,6 +29,11 @@ const StyledWrapper = styled.div`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
+
+ &.scratch-collection {
+ font-weight: 600;
+ font-size: 15px;
+ }
}
.tab-count {
diff --git a/packages/bruno-app/src/components/RequestTabs/CollectionHeader/index.js b/packages/bruno-app/src/components/RequestTabs/CollectionHeader/index.js
index a377e2d97..241f5a24f 100644
--- a/packages/bruno-app/src/components/RequestTabs/CollectionHeader/index.js
+++ b/packages/bruno-app/src/components/RequestTabs/CollectionHeader/index.js
@@ -325,8 +325,7 @@ const CollectionHeader = ({ collection, isScratchCollection }) => {
icon={(
)}
diff --git a/packages/bruno-app/src/components/SaveTransientRequest/CollectionListItem/index.js b/packages/bruno-app/src/components/SaveTransientRequest/CollectionListItem/index.js
index cb9826013..8d33eec43 100644
--- a/packages/bruno-app/src/components/SaveTransientRequest/CollectionListItem/index.js
+++ b/packages/bruno-app/src/components/SaveTransientRequest/CollectionListItem/index.js
@@ -1,6 +1,6 @@
import React, { useMemo, useCallback, memo } from 'react';
import { useSelector } from 'react-redux';
-import { IconDatabase, IconCheck, IconLoader2 } from '@tabler/icons';
+import { IconDatabase, IconLoader2 } from '@tabler/icons';
import { areItemsLoading } from 'utils/collections';
const CollectionListItem = memo(({ collectionUid, collectionPath, collectionName, isSelected, onSelect }) => {
@@ -8,11 +8,10 @@ const CollectionListItem = memo(({ collectionUid, collectionPath, collectionName
state.collections.collections.find((c) => c.uid === collectionUid || c.pathname === collectionPath)
);
- const { isFullyLoaded, isLoading } = useMemo(() => {
+ const isLoading = useMemo(() => {
const isMounted = collection?.mountStatus === 'mounted';
const fullyLoaded = isMounted && !areItemsLoading(collection);
- const loading = isSelected && !fullyLoaded;
- return { isFullyLoaded: fullyLoaded, isLoading: loading };
+ return isSelected && !fullyLoaded;
}, [collection, isSelected]);
const handleClick = useCallback(() => {
@@ -33,9 +32,6 @@ const CollectionListItem = memo(({ collectionUid, collectionPath, collectionName
{isLoading && (
)}
- {isFullyLoaded && (
-
- )}
);
});
diff --git a/packages/bruno-app/src/components/SaveTransientRequest/StyledWrapper.js b/packages/bruno-app/src/components/SaveTransientRequest/StyledWrapper.js
index 90794cfb0..6a6fcf49e 100644
--- a/packages/bruno-app/src/components/SaveTransientRequest/StyledWrapper.js
+++ b/packages/bruno-app/src/components/SaveTransientRequest/StyledWrapper.js
@@ -204,7 +204,7 @@ const StyledWrapper = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
- padding: 16px 0px;
+ padding: 16px 0px 0px 0px;
background-color: ${(props) => props.theme.modal.body.bg};
border-top: 1px solid ${(props) => props.theme.border.border0};
border-bottom-left-radius: ${(props) => props.theme.border.radius.base};
@@ -370,6 +370,98 @@ const StyledWrapper = styled.div`
font-size: 12px;
margin-top: 4px;
}
+
+ /* New Collection Input Styles */
+ .new-collection-item {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ padding: 12px;
+ border-top: 1px solid ${(props) => props.theme.border.border1};
+ margin-top: 4px;
+
+ &:first-child {
+ border-top: none;
+ margin-top: 0;
+ }
+ }
+
+ .new-collection-field {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ }
+
+ .new-collection-label {
+ font-size: 13px;
+ font-weight: 500;
+ color: ${(props) => props.theme.text};
+ }
+
+ .new-collection-input {
+ width: 100%;
+ padding: 8px 10px;
+ border-radius: ${(props) => props.theme.border.radius.sm};
+ background-color: ${(props) => props.theme.input.bg};
+ border: 1px solid ${(props) => props.theme.input.border};
+ color: ${(props) => props.theme.text};
+ font-size: 14px;
+ transition: border-color ease-in-out 0.1s;
+
+ &:focus {
+ border: solid 1px ${(props) => props.theme.input.focusBorder} !important;
+ outline: none !important;
+ }
+
+ &::placeholder {
+ color: ${(props) => props.theme.colors.text.muted};
+ }
+
+ &.cursor-pointer {
+ cursor: pointer;
+ }
+ }
+
+ .new-collection-location-row {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+
+ .new-collection-select {
+ width: 100%;
+ padding: 8px 10px;
+ padding-right: 28px;
+ border-radius: ${(props) => props.theme.border.radius.sm};
+ background-color: ${(props) => props.theme.input.bg};
+ border: 1px solid ${(props) => props.theme.input.border};
+ color: ${(props) => props.theme.text};
+ font-size: 14px;
+ cursor: pointer;
+ transition: border-color ease-in-out 0.1s;
+ appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 10px center;
+
+ &:focus {
+ border: solid 1px ${(props) => props.theme.input.focusBorder} !important;
+ outline: none !important;
+ }
+ }
+
+ .new-collection-actions-footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 8px;
+ margin-top: 4px;
+ }
+
+ .collection-empty-state-subtitle {
+ font-size: 12px;
+ margin-top: 4px;
+ opacity: 0.8;
+ }
`;
export default StyledWrapper;
diff --git a/packages/bruno-app/src/components/SaveTransientRequest/index.js b/packages/bruno-app/src/components/SaveTransientRequest/index.js
index 994672ca9..7f49fd701 100644
--- a/packages/bruno-app/src/components/SaveTransientRequest/index.js
+++ b/packages/bruno-app/src/components/SaveTransientRequest/index.js
@@ -1,4 +1,4 @@
-import React, { useState, useMemo, useEffect, useRef, useCallback } from 'react';
+import React, { useState, useMemo, useEffect, useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import Modal from 'components/Modal';
import SearchInput from 'components/SearchInput';
@@ -14,7 +14,7 @@ import FolderBreadcrumbs from './FolderBreadcrumbs';
import useCollectionFolderTree from 'hooks/useCollectionFolderTree';
import { removeSaveTransientRequestModal } from 'providers/ReduxStore/slices/collections';
import { insertTaskIntoQueue } from 'providers/ReduxStore/slices/app';
-import { newFolder, closeTabs, mountCollection } from 'providers/ReduxStore/slices/collections/actions';
+import { newFolder, closeTabs, mountCollection, createCollection, browseDirectory } from 'providers/ReduxStore/slices/collections/actions';
import { sanitizeName, validateName, validateNameError } from 'utils/common/regex';
import { resolveRequestFilename } from 'utils/common/platform';
import path from 'utils/common/path';
@@ -23,6 +23,7 @@ import { DEFAULT_COLLECTION_FORMAT } from 'utils/common/constants';
import { itemSchema } from '@usebruno/schema';
import { uuid } from 'utils/common';
import { formatIpcError } from 'utils/common/error';
+import get from 'lodash/get';
const SaveTransientRequest = ({ item: itemProp, collection: collectionProp, isOpen = false, onClose }) => {
const dispatch = useDispatch();
@@ -39,6 +40,11 @@ const SaveTransientRequest = ({ item: itemProp, collection: collectionProp, isOp
const activeWorkspace = workspaces.find((w) => w.uid === activeWorkspaceUid);
const allCollections = useSelector((state) => state.collections.collections);
const isScratchCollection = activeWorkspace?.scratchCollectionUid === collection?.uid;
+ const preferences = useSelector((state) => state.app.preferences);
+ const isDefaultWorkspace = activeWorkspace?.type === 'default';
+ const defaultCollectionLocation = isDefaultWorkspace
+ ? get(preferences, 'general.defaultLocation', '')
+ : (activeWorkspace?.pathname ? `${activeWorkspace.pathname}/collections` : '');
const availableCollections = useMemo(() => {
if (!isScratchCollection || !activeWorkspace) return [];
@@ -66,7 +72,9 @@ const SaveTransientRequest = ({ item: itemProp, collection: collectionProp, isOp
const [showFilesystemName, setShowFilesystemName] = useState(false);
const [isEditingFolderFilename, setIsEditingFolderFilename] = useState(false);
const [pendingFolderNavigation, setPendingFolderNavigation] = useState(null);
- const newFolderInputRef = useRef(null);
+
+ // State for new collection creation
+ const [newCollection, setNewCollection] = useState({ show: false, name: '', location: '', format: DEFAULT_COLLECTION_FORMAT });
const [selectedTargetCollectionPath, setSelectedTargetCollectionPath] = useState(null);
const [isSelectingCollection, setIsSelectingCollection] = useState(isScratchCollection);
@@ -111,6 +119,8 @@ const SaveTransientRequest = ({ item: itemProp, collection: collectionProp, isOp
setPendingFolderNavigation(null);
setSelectedTargetCollectionPath(null);
setIsSelectingCollection(isScratchCollection);
+ // Reset new collection state
+ setNewCollection({ show: false, name: '', location: '', format: DEFAULT_COLLECTION_FORMAT });
}, [item?.name, isScratchCollection, reset]);
useEffect(() => {
@@ -119,12 +129,6 @@ const SaveTransientRequest = ({ item: itemProp, collection: collectionProp, isOp
}
}, [isOpen, item, resetForm]);
- useEffect(() => {
- if (showNewFolderInput && newFolderInputRef.current) {
- newFolderInputRef.current.focus();
- }
- }, [showNewFolderInput]);
-
useEffect(() => {
if (pendingFolderNavigation) {
const newFolder = currentFolders.find((f) => f.filename === pendingFolderNavigation);
@@ -298,6 +302,48 @@ const SaveTransientRequest = ({ item: itemProp, collection: collectionProp, isOp
}
};
+ // New Collection handlers
+ const handleShowNewCollection = () => {
+ setNewCollection({ show: true, name: '', location: defaultCollectionLocation, format: DEFAULT_COLLECTION_FORMAT });
+ };
+
+ const handleCancelNewCollection = () => {
+ setNewCollection({ show: false, name: '', location: '', format: DEFAULT_COLLECTION_FORMAT });
+ };
+
+ const handleBrowseCollectionLocation = () => {
+ dispatch(browseDirectory())
+ .then((dirPath) => {
+ if (typeof dirPath === 'string') {
+ setNewCollection((prev) => ({ ...prev, location: dirPath }));
+ }
+ })
+ .catch(() => {});
+ };
+
+ const handleCreateNewCollection = async () => {
+ const trimmedName = newCollection.name.trim();
+ if (!trimmedName) {
+ toast.error('Collection name is required');
+ return;
+ }
+ if (!validateName(trimmedName)) {
+ toast.error(validateNameError(trimmedName));
+ return;
+ }
+ if (!newCollection.location) {
+ toast.error('Location is required');
+ return;
+ }
+ try {
+ await dispatch(createCollection(trimmedName, sanitizeName(trimmedName), newCollection.location, { format: newCollection.format }));
+ toast.success('Collection created!');
+ handleCancelNewCollection();
+ } catch (err) {
+ toast.error(err?.message || 'An error occurred while creating the collection');
+ }
+ };
+
const handleFolderClick = (folderUid) => {
navigateIntoFolder(folderUid);
setSearchText('');
@@ -377,7 +423,7 @@ const SaveTransientRequest = ({ item: itemProp, collection: collectionProp, isOp
{isSelectingCollection ? (
- {availableCollections.length > 0 ? (
+ {availableCollections.length > 0 || newCollection.show ? (
{availableCollections.map((coll) => {
const collPath = coll.path || coll.pathname;
@@ -392,10 +438,117 @@ const SaveTransientRequest = ({ item: itemProp, collection: collectionProp, isOp
/>
);
})}
+ {newCollection.show && (
+ -
+
+
+ node?.focus()}
+ type="text"
+ className="new-collection-input"
+ placeholder="Enter collection name"
+ value={newCollection.name}
+ onChange={(e) => setNewCollection((prev) => ({ ...prev, name: e.target.value }))}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ e.stopPropagation();
+ handleCreateNewCollection();
+ } else if (e.key === 'Escape') {
+ e.stopPropagation();
+ handleCancelNewCollection();
+ }
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
) : (
- No collections available in workspace. Please add a collection to the workspace first.
+
No collections Yet
+
Collections help you organize your requests. Create your first one to save this request.
)}
@@ -448,7 +601,7 @@ const SaveTransientRequest = ({ item: itemProp, collection: collectionProp, isOp
node?.focus()}
type="text"
className="new-folder-input"
placeholder="Untitled new folder"
@@ -595,6 +748,17 @@ const SaveTransientRequest = ({ item: itemProp, collection: collectionProp, isOp
New Folder
)}
+ {isSelectingCollection && !newCollection.show && (
+ }
+ onClick={handleShowNewCollection}
+ >
+ New collection
+
+ )}