diff --git a/packages/bruno-app/src/components/AppTitleBar/index.js b/packages/bruno-app/src/components/AppTitleBar/index.js
index a54f942df..0c9ec1f8d 100644
--- a/packages/bruno-app/src/components/AppTitleBar/index.js
+++ b/packages/bruno-app/src/components/AppTitleBar/index.js
@@ -1,10 +1,10 @@
import React from 'react';
-import { IconCheck, IconChevronDown, IconFolder, IconHome, IconPin, IconPinned, IconPlus, IconUpload } from '@tabler/icons';
+import { IconCheck, IconChevronDown, IconFolder, IconHome, IconPin, IconPinned, IconPlus, IconUpload, IconSettings } from '@tabler/icons';
import { forwardRef, useCallback, useEffect, useMemo, useState } from 'react';
import toast from 'react-hot-toast';
import { useDispatch, useSelector } from 'react-redux';
-import { savePreferences, showHomePage, toggleSidebarCollapse } from 'providers/ReduxStore/slices/app';
+import { savePreferences, showHomePage, showManageWorkspacePage, toggleSidebarCollapse } from 'providers/ReduxStore/slices/app';
import { closeConsole, openConsole } from 'providers/ReduxStore/slices/logs';
import { openWorkspaceDialog, switchWorkspace } from 'providers/ReduxStore/slices/workspaces/actions';
import { sortWorkspaces, toggleWorkspacePin } from 'utils/workspaces';
@@ -90,6 +90,10 @@ const AppTitleBar = () => {
setCreateWorkspaceModalOpen(true);
};
+ const handleManageWorkspaces = () => {
+ dispatch(showManageWorkspacePage());
+ };
+
const handleImportWorkspace = () => {
setImportWorkspaceModalOpen(true);
};
@@ -166,6 +170,12 @@ const AppTitleBar = () => {
leftSection: IconUpload,
label: 'Import workspace',
onClick: handleImportWorkspace
+ },
+ {
+ id: 'manage-workspaces',
+ leftSection: IconSettings,
+ label: 'Manage workspaces',
+ onClick: handleManageWorkspaces
}
);
diff --git a/packages/bruno-app/src/components/ManageWorkspace/DeleteWorkspace/index.js b/packages/bruno-app/src/components/ManageWorkspace/DeleteWorkspace/index.js
new file mode 100644
index 000000000..1eea605ab
--- /dev/null
+++ b/packages/bruno-app/src/components/ManageWorkspace/DeleteWorkspace/index.js
@@ -0,0 +1,55 @@
+import React, { useState } from 'react';
+import Portal from 'components/Portal/index';
+import Modal from 'components/Modal/index';
+import toast from 'react-hot-toast';
+import { useDispatch } from 'react-redux';
+import { IconFolder } from '@tabler/icons';
+import { closeWorkspaceAction } from 'providers/ReduxStore/slices/workspaces/actions';
+
+const DeleteWorkspace = ({ onClose, workspace }) => {
+ const dispatch = useDispatch();
+ const [isDeleting, setIsDeleting] = useState(false);
+
+ const onConfirm = async () => {
+ if (isDeleting) return;
+
+ try {
+ setIsDeleting(true);
+ await dispatch(closeWorkspaceAction(workspace.uid));
+ onClose();
+ } catch (error) {
+ toast.error(error?.message || 'An error occurred while removing the workspace');
+ setIsDeleting(false);
+ }
+ };
+
+ return (
+
+
+
+
+ {workspace?.name}
+
+ {workspace?.pathname && (
+ {workspace.pathname}
+ )}
+
+ Are you sure you want to remove workspace {workspace?.name}?
+
+
+ The workspace will still be available in the file system and can be re-opened later.
+
+
+
+ );
+};
+
+export default DeleteWorkspace;
diff --git a/packages/bruno-app/src/components/ManageWorkspace/RenameWorkspace/index.js b/packages/bruno-app/src/components/ManageWorkspace/RenameWorkspace/index.js
new file mode 100644
index 000000000..d4c4c8045
--- /dev/null
+++ b/packages/bruno-app/src/components/ManageWorkspace/RenameWorkspace/index.js
@@ -0,0 +1,95 @@
+import React, { useEffect, useRef } from 'react';
+import Portal from 'components/Portal/index';
+import Modal from 'components/Modal/index';
+import toast from 'react-hot-toast';
+import { useFormik } from 'formik';
+import * as Yup from 'yup';
+import { useDispatch, useSelector } from 'react-redux';
+import { renameWorkspaceAction } from 'providers/ReduxStore/slices/workspaces/actions';
+
+const RenameWorkspace = ({ onClose, workspace }) => {
+ const dispatch = useDispatch();
+ const { workspaces } = useSelector((state) => state.workspaces);
+ const inputRef = useRef();
+
+ const formik = useFormik({
+ enableReinitialize: true,
+ initialValues: {
+ name: workspace.name
+ },
+ validationSchema: Yup.object({
+ name: Yup.string()
+ .min(1, 'must be at least 1 character')
+ .max(255, 'must be 255 characters or less')
+ .required('name is required')
+ .test('unique-name', 'A workspace with this name already exists', function (value) {
+ if (!value) return true;
+ return !workspaces.some((w) =>
+ w.uid !== workspace.uid && w.name.toLowerCase() === value.toLowerCase()
+ );
+ })
+ }),
+ onSubmit: (values) => {
+ if (values.name === workspace.name) {
+ onClose();
+ return;
+ }
+ dispatch(renameWorkspaceAction(workspace.uid, values.name))
+ .then(() => {
+ onClose();
+ })
+ .catch((error) => {
+ toast.error(error?.message || 'An error occurred while renaming the workspace');
+ });
+ }
+ });
+
+ useEffect(() => {
+ if (inputRef && inputRef.current) {
+ inputRef.current.focus();
+ inputRef.current.select();
+ }
+ }, [inputRef]);
+
+ const onSubmit = () => {
+ formik.handleSubmit();
+ };
+
+ return (
+
+
+
+
+
+ );
+};
+
+export default RenameWorkspace;
diff --git a/packages/bruno-app/src/components/ManageWorkspace/StyledWrapper.js b/packages/bruno-app/src/components/ManageWorkspace/StyledWrapper.js
new file mode 100644
index 000000000..f049cc1f4
--- /dev/null
+++ b/packages/bruno-app/src/components/ManageWorkspace/StyledWrapper.js
@@ -0,0 +1,175 @@
+import styled from 'styled-components';
+
+const StyledWrapper = styled.div`
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+
+ .manage-workspace-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 12px 16px;
+ border-bottom: 1px solid ${(props) => props.theme.workspace.border};
+ }
+
+ .header-left {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+
+ .back-button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 4px;
+ cursor: pointer;
+ color: ${(props) => props.theme.text};
+ }
+
+ .header-title {
+ font-size: 15px;
+ font-weight: 600;
+ color: ${(props) => props.theme.text};
+ }
+
+ .create-workspace-btn {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 6px 12px;
+ border-radius: ${(props) => props.theme.border.radius.base};
+ background: ${(props) => props.theme.workspace.accent};
+ color: white;
+ font-size: ${(props) => props.theme.font.size.sm};
+ font-weight: 500;
+ cursor: pointer;
+ border: none;
+ }
+
+ .workspace-list {
+ flex: 1;
+ overflow-y: auto;
+ padding: 0 16px;
+ }
+
+ .workspace-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 12px 0;
+ border-bottom: 1px solid ${(props) => props.theme.workspace.border};
+ }
+
+ .workspace-info {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ flex: 1;
+ min-width: 0;
+ }
+
+ .workspace-name-row {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ }
+
+ .workspace-icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+
+ &.default {
+ color: ${(props) => props.theme.colors.text.muted};
+ }
+
+ &.regular {
+ color: ${(props) => props.theme.workspace.accent};
+ }
+ }
+
+ .workspace-name {
+ font-size: ${(props) => props.theme.font.size.md};
+ font-weight: 500;
+ color: ${(props) => props.theme.text};
+ }
+
+ .default-badge {
+ padding: 1px 6px;
+ border-radius: ${(props) => props.theme.border.radius.sm};
+ background: ${(props) => props.theme.sidebar.badge.bg};
+ color: ${(props) => props.theme.colors.text.muted};
+ font-size: ${(props) => props.theme.font.size.xs};
+ font-weight: 500;
+ }
+
+ .workspace-path {
+ font-size: ${(props) => props.theme.font.size.xs};
+ color: ${(props) => props.theme.colors.text.muted};
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .workspace-actions {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+
+ .action-btn {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ padding: 4px 8px;
+ background: transparent;
+ border: none;
+ color: ${(props) => props.theme.text};
+ font-size: ${(props) => props.theme.font.size.xs};
+ cursor: pointer;
+ }
+
+ .more-actions-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 4px;
+ background: transparent;
+ border: none;
+ color: ${(props) => props.theme.text};
+ cursor: pointer;
+ }
+
+ .dropdown-menu {
+ min-width: 120px;
+ }
+
+ .dropdown-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 12px;
+ cursor: pointer;
+ color: ${(props) => props.theme.text};
+ font-size: ${(props) => props.theme.font.size.sm};
+
+ &.danger {
+ color: ${(props) => props.theme.colors.text.danger};
+ }
+ }
+
+ .empty-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 200px;
+ color: ${(props) => props.theme.colors.text.muted};
+ font-size: ${(props) => props.theme.font.size.sm};
+ }
+`;
+
+export default StyledWrapper;
diff --git a/packages/bruno-app/src/components/ManageWorkspace/index.js b/packages/bruno-app/src/components/ManageWorkspace/index.js
new file mode 100644
index 000000000..f0e6622b7
--- /dev/null
+++ b/packages/bruno-app/src/components/ManageWorkspace/index.js
@@ -0,0 +1,162 @@
+import React, { useState, useMemo } from 'react';
+import { useSelector, useDispatch } from 'react-redux';
+import { IconArrowLeft, IconPlus, IconFolder, IconLock, IconDots, IconCategory, IconLogin } from '@tabler/icons';
+import toast from 'react-hot-toast';
+
+import { showHomePage } from 'providers/ReduxStore/slices/app';
+import { switchWorkspace } from 'providers/ReduxStore/slices/workspaces/actions';
+import { showInFolder } from 'providers/ReduxStore/slices/collections/actions';
+import { sortWorkspaces } from 'utils/workspaces';
+
+import CreateWorkspace from 'components/WorkspaceSidebar/CreateWorkspace';
+import RenameWorkspace from './RenameWorkspace';
+import DeleteWorkspace from './DeleteWorkspace';
+import StyledWrapper from './StyledWrapper';
+import MenuDropdown from 'ui/MenuDropdown/index';
+
+const ManageWorkspace = () => {
+ const dispatch = useDispatch();
+ const { workspaces, activeWorkspaceUid } = useSelector((state) => state.workspaces);
+ const preferences = useSelector((state) => state.app.preferences);
+
+ const [createWorkspaceModalOpen, setCreateWorkspaceModalOpen] = useState(false);
+ const [renameWorkspaceModal, setRenameWorkspaceModal] = useState({ open: false, workspace: null });
+ const [deleteWorkspaceModal, setDeleteWorkspaceModal] = useState({ open: false, workspace: null });
+
+ const sortedWorkspaces = useMemo(() => {
+ return sortWorkspaces(workspaces, preferences);
+ }, [workspaces, preferences]);
+
+ const handleBack = () => {
+ dispatch(showHomePage());
+ };
+
+ const handleOpenWorkspace = (workspace) => {
+ dispatch(switchWorkspace(workspace.uid));
+ dispatch(showHomePage());
+ toast.success(`Switched to ${workspace.name}`);
+ };
+
+ const handleShowInFolder = (workspace) => {
+ if (workspace.pathname) {
+ dispatch(showInFolder(workspace.pathname)).catch(() => {
+ toast.error('Error opening the folder');
+ });
+ }
+ };
+
+ const handleRenameClick = (workspace) => {
+ setRenameWorkspaceModal({ open: true, workspace });
+ };
+
+ const handleCloseClick = (workspace) => {
+ if (workspace.type === 'default') {
+ toast.error('Cannot remove the default workspace');
+ return;
+ }
+ setDeleteWorkspaceModal({ open: true, workspace });
+ };
+
+ return (
+
+ {createWorkspaceModalOpen && (
+ setCreateWorkspaceModalOpen(false)} />
+ )}
+
+ {renameWorkspaceModal.open && renameWorkspaceModal.workspace && (
+ setRenameWorkspaceModal({ open: false, workspace: null })}
+ />
+ )}
+
+ {deleteWorkspaceModal.open && deleteWorkspaceModal.workspace && (
+ setDeleteWorkspaceModal({ open: false, workspace: null })}
+ />
+ )}
+
+
+
+
+
+
+
Manage Workspace
+
+
+
+
+
+ {sortedWorkspaces.length === 0 ? (
+
+ No workspaces found
+
+ ) : (
+ sortedWorkspaces.map((workspace) => {
+ const isDefault = workspace.type === 'default';
+ const isActive = workspace.uid === activeWorkspaceUid;
+
+ return (
+
+
+
+
+ {isDefault ? (
+
+ ) : (
+
+ )}
+
+ {workspace.name}
+ {isDefault && Default}
+
+ {workspace.pathname && (
+
{workspace.pathname}
+ )}
+
+
+
+
+ {workspace.pathname && workspace.type !== 'default' && (
+
+ )}
+ {!isDefault && (
+ handleRenameClick(workspace) },
+ { id: 'remove', label: 'Remove', onClick: () => handleCloseClick(workspace) }
+ ]}
+ >
+
+
+ )}
+
+
+ );
+ })
+ )}
+
+
+ );
+};
+
+export default ManageWorkspace;
diff --git a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/CreateEnvironment/index.js b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/CreateEnvironment/index.js
index 7899c807a..a286108ad 100644
--- a/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/CreateEnvironment/index.js
+++ b/packages/bruno-app/src/components/WorkspaceHome/WorkspaceEnvironments/CreateEnvironment/index.js
@@ -69,7 +69,7 @@ const CreateEnvironment = ({ onClose, onEnvironmentCreated }) => {
>