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 <siddharth@usebruno.com>
This commit is contained in:
Chirag Chandrashekhar
2025-11-17 12:09:12 +05:30
committed by GitHub
parent 8ec1925b9f
commit 2be602d16c
2 changed files with 139 additions and 2 deletions

View File

@@ -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 (
<Modal
size="md"
title="Close Collection"
confirmText="Save and Close"
cancelText="Close without saving"
handleCancel={onClose}
disableEscapeKey={true}
disableCloseOnOutsideClick={true}
closeModalFadeTimeout={150}
hideFooter={true}
>
<div className="flex items-center">
<IconAlertTriangle size={32} strokeWidth={1.5} className="text-yellow-600" />
<h1 className="ml-2 text-lg font-semibold">Hold on..</h1>
</div>
<p className="mt-4">
Do you want to save the changes you made to the following{' '}
<span className="font-medium">{currentDrafts.length}</span> {pluralizeWord('request', currentDrafts.length)}?
</p>
<ul className="mt-4">
{currentDrafts.slice(0, MAX_UNSAVED_REQUESTS_TO_SHOW).map((item) => {
return (
<li key={item.uid} className="mt-1 text-xs">
{item.filename}
</li>
);
})}
</ul>
{currentDrafts.length > MAX_UNSAVED_REQUESTS_TO_SHOW && (
<p className="mt-1 text-xs">
...{currentDrafts.length - MAX_UNSAVED_REQUESTS_TO_SHOW} additional{' '}
{pluralizeWord('request', currentDrafts.length - MAX_UNSAVED_REQUESTS_TO_SHOW)} not shown
</p>
)}
<div className="flex justify-between mt-6">
<div>
<button className="btn btn-sm btn-danger" onClick={handleDiscardAll}>
Discard and Close
</button>
</div>
<div>
<button className="btn btn-close btn-sm mr-2" onClick={onClose}>
Cancel
</button>
<button className="btn btn-secondary btn-sm" onClick={handleSaveAll}>
{currentDrafts.length > 1 ? 'Save All and Close' : 'Save and Close'}
</button>
</div>
</div>
</Modal>
);
};
export default ConfirmCollectionCloseDrafts;

View File

@@ -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 <ConfirmCollectionCloseDrafts onClose={onClose} collection={collection} collectionUid={collectionUid} />;
}
// Otherwise, show the standard close confirmation modal
return (
<Modal size="sm" title="Close Collection" confirmText="Close" handleConfirm={onConfirm} handleCancel={onClose}>
<div className="flex items-center">