feat: add functionality to create new HTTP requests from the welcome modal and collections section (#7350)

This commit is contained in:
naman-bruno
2026-03-03 14:42:39 +05:30
committed by GitHub
parent ca0412b58b
commit 4b15b14cf7
6 changed files with 77 additions and 13 deletions

View File

@@ -16,11 +16,13 @@ import {
IconTerminal2
} from '@tabler/icons';
import { importCollection, openCollection, importCollectionFromZip } from 'providers/ReduxStore/slices/collections/actions';
import { importCollection, openCollection, importCollectionFromZip, newHttpRequest } from 'providers/ReduxStore/slices/collections/actions';
import { sortCollections } from 'providers/ReduxStore/slices/collections/index';
import { savePreferences } from 'providers/ReduxStore/slices/app';
import { normalizePath } from 'utils/common/path';
import { isScratchCollection } from 'utils/collections';
import { isScratchCollection, flattenItems, isItemTransientRequest } from 'utils/collections';
import { sanitizeName } from 'utils/common/regex';
import filter from 'lodash/filter';
import MenuDropdown from 'ui/MenuDropdown';
import ActionIcon from 'ui/ActionIcon';
@@ -179,6 +181,50 @@ const CollectionsSection = () => {
});
};
const handleStartRequest = () => {
const scratchCollectionUid = activeWorkspace?.scratchCollectionUid;
if (!scratchCollectionUid) {
toast.error('Unable to create request');
return;
}
const scratchCollection = collections.find((c) => c.uid === scratchCollectionUid);
if (!scratchCollection) {
toast.error('Unable to create request');
return;
}
const allItems = flattenItems(scratchCollection.items || []);
const transientRequests = filter(allItems, (item) => isItemTransientRequest(item));
let maxNumber = 0;
transientRequests.forEach((item) => {
const match = item.name?.match(/^Untitled (\d+)$/);
if (match) {
const number = parseInt(match[1], 10);
if (number > maxNumber) {
maxNumber = number;
}
}
});
const requestName = `Untitled ${maxNumber + 1}`;
const filename = sanitizeName(requestName);
dispatch(
newHttpRequest({
requestName,
filename,
requestType: 'http-request',
requestUrl: '',
requestMethod: 'GET',
collectionUid: scratchCollectionUid,
itemUid: null,
isTransient: true
})
).catch((err) => {
toast.error('An error occurred while creating the request');
});
};
const addDropdownItems = [
{
id: 'create',
@@ -289,6 +335,10 @@ const CollectionsSection = () => {
handleDismissWelcomeModal();
handleOpenCollection();
}}
onStartRequest={() => {
handleDismissWelcomeModal();
handleStartRequest();
}}
/>
)}
{createCollectionModalOpen && (

View File

@@ -76,8 +76,12 @@ const StyledWrapper = styled.div`
transition: all 0.15s ease;
&:hover {
background: ${(props) => props.theme.sidebar.collection.item.hoverBg};
border-color: ${(props) => props.theme.border.border1};
border-color: ${(props) => props.theme.primary.subtle};
background: ${(props) => rgba(props.theme.primary.solid, 0.06)};
}
&:active {
transform: scale(0.98);
}
.secondary-icon {

View File

@@ -1,8 +1,8 @@
import React from 'react';
import { IconPlus, IconDownload, IconFileImport } from '@tabler/icons';
import { IconPlus, IconDownload, IconFileImport, IconSend } from '@tabler/icons';
import StyledWrapper from './StyledWrapper';
const GetStartedStep = ({ onCreateCollection, onImportCollection, onOpenCollection }) => (
const GetStartedStep = ({ onCreateCollection, onImportCollection, onOpenCollection, onStartRequest }) => (
<StyledWrapper className="step-body">
<div className="step-label">Your first collection</div>
<div className="step-title">You're all set! What's next?</div>
@@ -38,6 +38,15 @@ const GetStartedStep = ({ onCreateCollection, onImportCollection, onOpenCollecti
<div className="secondary-desc">Open a Bruno collection from your filesystem</div>
</div>
</button>
<button className="secondary-action" onClick={onStartRequest}>
<span className="secondary-icon">
<IconSend size={16} stroke={1.5} />
</span>
<div>
<div className="secondary-label">Get started with a request</div>
<div className="secondary-desc">Jump right in with a new HTTP request</div>
</div>
</button>
</div>
</StyledWrapper>
);

View File

@@ -6,7 +6,7 @@ const StorageStep = ({ collectionLocation, onBrowse }) => (
<div className="step-label">Storage</div>
<div className="step-title">Where should we store your collections?</div>
<div className="step-description">
Bruno saves collections as plain files on your filesystem perfect for version control with Git.
Bruno saves collections as plain files on your filesystem, perfect for version control with Git.
</div>
<div className="location-input-group">
@@ -31,7 +31,7 @@ const StorageStep = ({ collectionLocation, onBrowse }) => (
</div>
</div>
<div className="location-hint">
Each collection gets its own folder inside this directory. You can change this per-collection later.
Each collection and workspace gets its own folder inside this directory. You can change this later.
</div>
</StyledWrapper>
);

View File

@@ -10,8 +10,8 @@ import StyledWrapper from './StyledWrapper';
const highlights = [
{
icon: IconFolderTabler,
title: 'Filesystem-first',
desc: 'Collections are plain files on your disk. No cloud sync, no proprietary lock-in. Your data stays yours.'
title: 'Filesystem only',
desc: 'Collections are plain files on your disk. No cloud sync, no proprietary lock-in.'
},
{
icon: IconGitFork,
@@ -21,12 +21,12 @@ const highlights = [
{
icon: IconLock,
title: 'Privacy-focused',
desc: 'No accounts required. No telemetry. Bruno works entirely offline your API keys never leave your machine.'
desc: 'No account, no login. Bruno works entirely offline, your API keys never leave your machine.'
},
{
icon: IconRocket,
title: 'Fast and lightweight',
desc: 'Built to be snappy. No bloated runtimes just a fast, focused tool for exploring and testing APIs.'
desc: 'Built to be snappy. No bloated runtimes, just a fast, focused tool for exploring and testing APIs.'
}
];

View File

@@ -15,7 +15,7 @@ import StyledWrapper from './StyledWrapper';
const TOTAL_STEPS = 4;
const WelcomeModal = ({ onDismiss, onImportCollection, onCreateCollection, onOpenCollection }) => {
const WelcomeModal = ({ onDismiss, onImportCollection, onCreateCollection, onOpenCollection, onStartRequest }) => {
const dispatch = useDispatch();
const preferences = useSelector((state) => state.app.preferences);
const defaultLocation = get(preferences, 'general.defaultLocation', '');
@@ -93,6 +93,7 @@ const WelcomeModal = ({ onDismiss, onImportCollection, onCreateCollection, onOpe
onCreateCollection={handleActionAndDismiss(onCreateCollection)}
onImportCollection={handleActionAndDismiss(onImportCollection)}
onOpenCollection={handleActionAndDismiss(onOpenCollection)}
onStartRequest={handleActionAndDismiss(onStartRequest)}
/>
];