diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/CopyEnvironment/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/CopyEnvironment/index.js
new file mode 100644
index 000000000..87b833e40
--- /dev/null
+++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/CopyEnvironment/index.js
@@ -0,0 +1,75 @@
+import Modal from 'components/Modal/index';
+import Portal from 'components/Portal/index';
+import { useFormik } from 'formik';
+import { copyEnvironment } from 'providers/ReduxStore/slices/collections/actions';
+import { useEffect, useRef } from 'react';
+import toast from 'react-hot-toast';
+import { useDispatch } from 'react-redux';
+import * as Yup from 'yup';
+
+const CopyEnvironment = ({ collection, environment, onClose }) => {
+ const dispatch = useDispatch();
+ const inputRef = useRef();
+ const formik = useFormik({
+ enableReinitialize: true,
+ initialValues: {
+ name: environment.name + ' - Copy'
+ },
+ validationSchema: Yup.object({
+ name: Yup.string()
+ .min(1, 'must be atleast 1 characters')
+ .max(50, 'must be 50 characters or less')
+ .required('name is required')
+ }),
+ onSubmit: (values) => {
+ dispatch(copyEnvironment(values.name, environment.uid, collection.uid))
+ .then(() => {
+ toast.success('Environment created in collection');
+ onClose();
+ })
+ .catch(() => toast.error('An error occurred while created the environment'));
+ }
+ });
+
+ useEffect(() => {
+ if (inputRef && inputRef.current) {
+ inputRef.current.focus();
+ }
+ }, [inputRef]);
+
+ const onSubmit = () => {
+ formik.handleSubmit();
+ };
+
+ return (
+
+
+
+
+
+ );
+};
+
+export default CopyEnvironment;
diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js
index 8db0d0418..f8b9e364e 100644
--- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js
+++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/index.js
@@ -1,12 +1,14 @@
-import React, { useState } from 'react';
-import { IconEdit, IconTrash, IconDatabase } from '@tabler/icons';
-import EnvironmentVariables from './EnvironmentVariables';
-import RenameEnvironment from '../../RenameEnvironment';
+import { IconCopy, IconDatabase, IconEdit, IconTrash } from '@tabler/icons';
+import { useState } from 'react';
+import CopyEnvironment from '../../CopyEnvironment';
import DeleteEnvironment from '../../DeleteEnvironment';
+import RenameEnvironment from '../../RenameEnvironment';
+import EnvironmentVariables from './EnvironmentVariables';
const EnvironmentDetails = ({ environment, collection }) => {
const [openEditModal, setOpenEditModal] = useState(false);
const [openDeleteModal, setOpenDeleteModal] = useState(false);
+ const [openCopyModal, setOpenCopyModal] = useState(false);
return (
@@ -20,6 +22,9 @@ const EnvironmentDetails = ({ environment, collection }) => {
collection={collection}
/>
)}
+ {openCopyModal && (
+
setOpenCopyModal(false)} environment={environment} collection={collection} />
+ )}
@@ -27,6 +32,7 @@ const EnvironmentDetails = ({ environment, collection }) => {
setOpenEditModal(true)} />
+ setOpenCopyModal(true)} />
setOpenDeleteModal(true)} />
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
index ccdd7fe1a..d806a3836 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
@@ -646,6 +646,37 @@ export const addEnvironment = (name, collectionUid) => (dispatch, getState) => {
});
};
+export const copyEnvironment = (name, baseEnvUid, collectionUid) => (dispatch, getState) => {
+ return new Promise((resolve, reject) => {
+ const state = getState();
+ const collection = findCollectionByUid(state.collections.collections, collectionUid);
+ if (!collection) {
+ return reject(new Error('Collection not found'));
+ }
+
+ const baseEnv = findEnvironmentInCollection(collection, baseEnvUid);
+ if (!collection) {
+ return reject(new Error('Environmnent not found'));
+ }
+
+ ipcRenderer
+ .invoke('renderer:copy-environment', collection.pathname, name, baseEnv.variables)
+ .then(
+ dispatch(
+ updateLastAction({
+ collectionUid,
+ lastAction: {
+ type: 'ADD_ENVIRONMENT',
+ payload: name
+ }
+ })
+ )
+ )
+ .then(resolve)
+ .catch(reject);
+ });
+};
+
export const renameEnvironment = (newName, environmentUid, collectionUid) => (dispatch, getState) => {
return new Promise((resolve, reject) => {
const state = getState();
diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js
index ae85558af..864aff82e 100644
--- a/packages/bruno-electron/src/ipc/collection.js
+++ b/packages/bruno-electron/src/ipc/collection.js
@@ -151,6 +151,28 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
}
});
+ // copy environment
+ ipcMain.handle('renderer:copy-environment', async (event, collectionPathname, name, baseVariables) => {
+ try {
+ const envDirPath = path.join(collectionPathname, 'environments');
+ if (!fs.existsSync(envDirPath)) {
+ await createDirectory(envDirPath);
+ }
+
+ const envFilePath = path.join(envDirPath, `${name}.bru`);
+ if (fs.existsSync(envFilePath)) {
+ throw new Error(`environment: ${envFilePath} already exists`);
+ }
+
+ const content = envJsonToBru({
+ variables: baseVariables
+ });
+ await writeFile(envFilePath, content);
+ } catch (error) {
+ return Promise.reject(error);
+ }
+ });
+
// save environment
ipcMain.handle('renderer:save-environment', async (event, collectionPathname, environment) => {
try {