diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseActions/StyledWrapper.js b/packages/bruno-app/src/components/ResponsePane/ResponseActions/StyledWrapper.js
new file mode 100644
index 000000000..6ba69090e
--- /dev/null
+++ b/packages/bruno-app/src/components/ResponsePane/ResponseActions/StyledWrapper.js
@@ -0,0 +1,29 @@
+import styled from 'styled-components';
+
+const StyledWrapper = styled.div`
+ button {
+ color: var(--color-tab-inactive);
+ cursor: pointer;
+
+ &:hover {
+ color: var(--color-tab-active);
+ }
+
+ &:disabled {
+ cursor: not-allowed;
+ opacity: 0.5;
+ }
+ }
+
+ .cursor-pointer {
+ display: flex;
+ align-items: center;
+ color: var(--color-tab-inactive);
+
+ &:hover {
+ color: var(--color-tab-active);
+ }
+ }
+`;
+
+export default StyledWrapper;
diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseActions/index.js b/packages/bruno-app/src/components/ResponsePane/ResponseActions/index.js
new file mode 100644
index 000000000..a430153a0
--- /dev/null
+++ b/packages/bruno-app/src/components/ResponsePane/ResponseActions/index.js
@@ -0,0 +1,35 @@
+import React, { useRef, forwardRef } from 'react';
+import { IconDots } from '@tabler/icons';
+import Dropdown from 'components/Dropdown';
+import StyledWrapper from './StyledWrapper';
+import ResponseClear from 'src/components/ResponsePane/ResponseClear';
+import ResponseSave from 'src/components/ResponsePane/ResponseSave';
+
+const ResponseActions = ({ collection, item }) => {
+ const menuDropdownTippyRef = useRef();
+
+ const onMenuDropdownCreate = (ref) => (menuDropdownTippyRef.current = ref);
+
+ const MenuIcon = forwardRef((_props, ref) => {
+ return (
+
+
+
+ );
+ });
+
+ const handleClose = () => {
+ menuDropdownTippyRef.current.hide();
+ };
+
+ return (
+
+ } placement="bottom-end">
+
+
+
+
+ );
+};
+
+export default ResponseActions;
diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js b/packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js
index 747543347..b18418592 100644
--- a/packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js
+++ b/packages/bruno-app/src/components/ResponsePane/ResponseClear/index.js
@@ -4,10 +4,11 @@ import { useDispatch } from 'react-redux';
import StyledWrapper from './StyledWrapper';
import { responseCleared } from 'providers/ReduxStore/slices/collections/index';
-const ResponseClear = ({ collection, item }) => {
+const ResponseClear = ({ collection, item, asDropdownItem, onClose }) => {
const dispatch = useDispatch();
- const clearResponse = () =>
+ const clearResponse = () => {
+ if (onClose) onClose();
dispatch(
responseCleared({
itemUid: item.uid,
@@ -15,6 +16,16 @@ const ResponseClear = ({ collection, item }) => {
response: null
})
);
+ };
+
+ if (asDropdownItem) {
+ return (
+
+
+ Clear
+
+ );
+ }
return (
diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js b/packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js
new file mode 100644
index 000000000..8c32a8bab
--- /dev/null
+++ b/packages/bruno-app/src/components/ResponsePane/ResponseCopy/StyledWrapper.js
@@ -0,0 +1,8 @@
+import styled from 'styled-components';
+
+const StyledWrapper = styled.div`
+ font-size: 0.8125rem;
+ color: ${(props) => props.theme.requestTabPanel.responseStatus};
+`;
+
+export default StyledWrapper;
diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js b/packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js
new file mode 100644
index 000000000..7c59301ef
--- /dev/null
+++ b/packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js
@@ -0,0 +1,46 @@
+import React, { useState, useEffect } from 'react';
+import StyledWrapper from './StyledWrapper';
+import toast from 'react-hot-toast';
+import { IconCopy, IconCheck } from '@tabler/icons';
+
+const ResponseCopy = ({ item }) => {
+ const response = item.response || {};
+ const [copied, setCopied] = useState(false);
+
+ useEffect(() => {
+ if (copied) {
+ const timer = setTimeout(() => {
+ setCopied(false);
+ }, 2000);
+ return () => clearTimeout(timer);
+ }
+ }, [copied]);
+
+ const copyResponse = async () => {
+ try {
+ const textToCopy = typeof response.data === 'string'
+ ? response.data
+ : JSON.stringify(response.data, null, 2);
+
+ await navigator.clipboard.writeText(textToCopy);
+ toast.success('Response copied to clipboard');
+ setCopied(true);
+ } catch (error) {
+ toast.error('Failed to copy response');
+ }
+ };
+
+ return (
+
+
+
+ );
+};
+
+export default ResponseCopy;
diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js b/packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js
index 15eef651b..c791463cd 100644
--- a/packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js
+++ b/packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js
@@ -4,11 +4,13 @@ import toast from 'react-hot-toast';
import get from 'lodash/get';
import { IconDownload } from '@tabler/icons';
-const ResponseSave = ({ item }) => {
+const ResponseSave = ({ item, asDropdownItem, onClose }) => {
const { ipcRenderer } = window;
const response = item.response || {};
const saveResponseToFile = () => {
+ if (!response.dataBuffer) return;
+ if (onClose) onClose();
return new Promise((resolve, reject) => {
ipcRenderer
.invoke('renderer:save-response-to-file', response, item?.requestSent?.url, item.pathname)
@@ -20,6 +22,20 @@ const ResponseSave = ({ item }) => {
});
};
+ if (asDropdownItem) {
+ return (
+
+
+ Download
+
+ );
+ }
+
return (