From 2be602d16c7590e0d53b3f49cdf7eb29cd2d3950 Mon Sep 17 00:00:00 2001 From: Chirag Chandrashekhar Date: Mon, 17 Nov 2025 12:09:12 +0530 Subject: [PATCH] Feature/prompt save before collection close (#6062) * added confirmation dialog before collection close for items in draft state * chore: lint fix --------- Co-authored-by: Sid --- .../ConfirmCollectionCloseDrafts.js | 122 ++++++++++++++++++ .../Collection/RemoveCollection/index.js | 19 ++- 2 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveCollection/ConfirmCollectionCloseDrafts.js diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveCollection/ConfirmCollectionCloseDrafts.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveCollection/ConfirmCollectionCloseDrafts.js new file mode 100644 index 000000000..2e4c7dc55 --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveCollection/ConfirmCollectionCloseDrafts.js @@ -0,0 +1,122 @@ +import React from 'react'; +import filter from 'lodash/filter'; +import { useDispatch } from 'react-redux'; +import { flattenItems, isItemARequest, hasRequestChanges } from 'utils/collections'; +import { pluralizeWord } from 'utils/common'; +import { saveMultipleRequests } from 'providers/ReduxStore/slices/collections/actions'; +import { deleteRequestDraft } from 'providers/ReduxStore/slices/collections'; +import { removeCollection } from 'providers/ReduxStore/slices/collections/actions'; +import { IconAlertTriangle } from '@tabler/icons'; +import Modal from 'components/Modal'; +import toast from 'react-hot-toast'; + +const ConfirmCollectionCloseDrafts = ({ onClose, collection, collectionUid }) => { + const MAX_UNSAVED_REQUESTS_TO_SHOW = 5; + const dispatch = useDispatch(); + + // Get all draft items in the collection + const currentDrafts = React.useMemo(() => { + if (!collection) return []; + const items = flattenItems(collection.items); + const collectionDrafts = filter(items, (item) => isItemARequest(item) && hasRequestChanges(item)); + return collectionDrafts.map((draft) => ({ + ...draft, + collectionUid: collectionUid + })); + }, [collection, collectionUid]); + + const handleSaveAll = () => { + dispatch(saveMultipleRequests(currentDrafts)) + .then(() => { + dispatch(removeCollection(collectionUid)) + .then(() => { + toast.success('Collection closed'); + onClose(); + }) + .catch(() => toast.error('An error occurred while closing the collection')); + }) + .catch(() => { + toast.error('Failed to save requests!'); + }); + }; + + const handleDiscardAll = () => { + // Discard all drafts + currentDrafts.forEach((draft) => { + dispatch(deleteRequestDraft({ + collectionUid: collectionUid, + itemUid: draft.uid + })); + }); + + // Then close the collection + dispatch(removeCollection(collectionUid)) + .then(() => { + toast.success('Collection closed'); + onClose(); + }) + .catch(() => toast.error('An error occurred while closing the collection')); + }; + + if (!currentDrafts.length) { + return null; + } + + return ( + +
+ +

Hold on..

+
+

+ Do you want to save the changes you made to the following{' '} + {currentDrafts.length} {pluralizeWord('request', currentDrafts.length)}? +

+ +
    + {currentDrafts.slice(0, MAX_UNSAVED_REQUESTS_TO_SHOW).map((item) => { + return ( +
  • + {item.filename} +
  • + ); + })} +
+ + {currentDrafts.length > MAX_UNSAVED_REQUESTS_TO_SHOW && ( +

+ ...{currentDrafts.length - MAX_UNSAVED_REQUESTS_TO_SHOW} additional{' '} + {pluralizeWord('request', currentDrafts.length - MAX_UNSAVED_REQUESTS_TO_SHOW)} not shown +

+ )} + +
+
+ +
+
+ + +
+
+
+ ); +}; + +export default ConfirmCollectionCloseDrafts; diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveCollection/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveCollection/index.js index 17b6dc007..44007deb6 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveCollection/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveCollection/index.js @@ -1,15 +1,24 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import toast from 'react-hot-toast'; import Modal from 'components/Modal'; import { useDispatch, useSelector } from 'react-redux'; import { IconFiles } from '@tabler/icons'; import { removeCollection } from 'providers/ReduxStore/slices/collections/actions'; -import { findCollectionByUid } from 'utils/collections/index'; +import { findCollectionByUid, flattenItems, isItemARequest, hasRequestChanges } from 'utils/collections/index'; +import filter from 'lodash/filter'; +import ConfirmCollectionCloseDrafts from './ConfirmCollectionCloseDrafts'; const RemoveCollection = ({ onClose, collectionUid }) => { const dispatch = useDispatch(); const collection = useSelector(state => findCollectionByUid(state.collections.collections, collectionUid)); + // Detect drafts in the collection + const drafts = useMemo(() => { + if (!collection) return []; + const items = flattenItems(collection.items); + return filter(items, (item) => isItemARequest(item) && hasRequestChanges(item)); + }, [collection]); + const onConfirm = () => { dispatch(removeCollection(collection.uid)) .then(() => { @@ -19,6 +28,12 @@ const RemoveCollection = ({ onClose, collectionUid }) => { .catch(() => toast.error('An error occurred while closing the collection')); }; + // If there are drafts, show the draft confirmation modal + if (drafts.length > 0) { + return ; + } + + // Otherwise, show the standard close confirmation modal return (