handle unsaved changes in dot env file editor (#7094)

* handle unsaved changes in dot env file editor

* fixes
This commit is contained in:
naman-bruno
2026-02-10 17:32:54 +05:30
committed by Bijin A B
parent 459620170a
commit 01e3999631
5 changed files with 86 additions and 10 deletions

View File

@@ -4,7 +4,14 @@ import Modal from 'components/Modal';
import Portal from 'components/Portal';
import Button from 'ui/Button';
const ConfirmCloseEnvironment = ({ onCancel, onCloseWithoutSave, onSaveAndClose, isGlobal }) => {
const ConfirmCloseEnvironment = ({ onCancel, onCloseWithoutSave, onSaveAndClose, isGlobal, isDotEnv }) => {
let settingsLabel = 'collection environment settings';
if (isDotEnv) {
settingsLabel = '.env file';
} else if (isGlobal) {
settingsLabel = 'global environment settings';
}
return (
<Portal>
<Modal
@@ -21,7 +28,7 @@ const ConfirmCloseEnvironment = ({ onCancel, onCloseWithoutSave, onSaveAndClose,
<h1 className="ml-2 text-lg font-medium">Hold on...</h1>
</div>
<div className="font-normal mt-4">
You have unsaved changes in {isGlobal ? 'global' : 'collection'} environment settings.
You have unsaved changes in {settingsLabel}.
</div>
<div className="flex justify-between mt-6">

View File

@@ -217,10 +217,12 @@ const DotEnvFileEditor = ({
];
formik.resetForm({ values: newValues });
setIsModified(false);
window.dispatchEvent(new Event('dotenv-save-complete'));
})
.catch((error) => {
console.error(error);
toast.error('An error occurred while saving the changes');
window.dispatchEvent(new Event('dotenv-save-failed'));
})
.finally(() => {
setIsSaving(false);
@@ -240,10 +242,12 @@ const DotEnvFileEditor = ({
.then(() => {
toast.success('Changes saved successfully');
setIsModified(false);
window.dispatchEvent(new Event('dotenv-save-complete'));
})
.catch((error) => {
console.error(error);
toast.error('An error occurred while saving the changes');
window.dispatchEvent(new Event('dotenv-save-failed'));
})
.finally(() => {
setIsSaving(false);

View File

@@ -22,6 +22,7 @@ import {
createDotEnvFile,
deleteDotEnvFile
} from 'providers/ReduxStore/slices/collections/actions';
import { setEnvironmentsDraft, clearEnvironmentsDraft } from 'providers/ReduxStore/slices/collections';
import { validateName, validateNameError } from 'utils/common/regex';
import toast from 'react-hot-toast';
import classnames from 'classnames';
@@ -72,11 +73,24 @@ const EnvironmentList = ({
const envUids = environments ? environments.map((env) => env.uid) : [];
const prevEnvUids = usePrevious(envUids);
const handleDotEnvModifiedChange = useCallback((modified) => {
setIsDotEnvModified(modified);
if (modified) {
dispatch(setEnvironmentsDraft({
collectionUid: collection.uid,
environmentUid: `dotenv:${selectedDotEnvFile}`,
variables: []
}));
} else {
dispatch(clearEnvironmentsDraft({ collectionUid: collection.uid }));
}
}, [dispatch, collection.uid, selectedDotEnvFile]);
useEffect(() => {
if (dotEnvFiles.length === 0) {
setSelectedDotEnvFile(null);
setActiveView('environment');
setIsDotEnvModified(false);
handleDotEnvModifiedChange(false);
return;
}
@@ -424,7 +438,7 @@ const EnvironmentList = ({
dispatch(deleteDotEnvFile(collection.uid, filename))
.then(() => {
toast.success(`${filename} file deleted!`);
setIsDotEnvModified(false);
handleDotEnvModifiedChange(false);
if (selectedDotEnvFile === filename) {
const remainingFiles = dotEnvFiles.filter((f) => f.filename !== filename);
if (remainingFiles.length > 0) {
@@ -467,7 +481,7 @@ const EnvironmentList = ({
onSave={handleSaveDotEnv}
onSaveRaw={handleSaveDotEnvRaw}
isModified={isDotEnvModified}
setIsModified={setIsDotEnvModified}
setIsModified={handleDotEnvModifiedChange}
dotEnvExists={selectedDotEnvData?.exists}
viewMode={dotEnvViewMode}
collection={collection}

View File

@@ -236,6 +236,7 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
{showConfirmEnvironmentClose && tab.type === 'environment-settings' && (
<ConfirmCloseEnvironment
isGlobal={false}
isDotEnv={collection.environmentsDraft?.environmentUid?.startsWith('dotenv:')}
onCancel={() => setShowConfirmEnvironmentClose(false)}
onCloseWithoutSave={() => {
dispatch(clearEnvironmentsDraft({ collectionUid: collection.uid }));
@@ -244,7 +245,25 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
}}
onSaveAndClose={() => {
const draft = collection.environmentsDraft;
if (draft?.environmentUid && draft?.variables) {
if (draft?.environmentUid?.startsWith('dotenv:')) {
const onSuccess = () => {
cleanup();
dispatch(clearEnvironmentsDraft({ collectionUid: collection.uid }));
dispatch(closeTabs({ tabUids: [tab.uid] }));
setShowConfirmEnvironmentClose(false);
};
const onFailed = () => {
cleanup();
setShowConfirmEnvironmentClose(false);
};
const cleanup = () => {
window.removeEventListener('dotenv-save-complete', onSuccess);
window.removeEventListener('dotenv-save-failed', onFailed);
};
window.addEventListener('dotenv-save-complete', onSuccess, { once: true });
window.addEventListener('dotenv-save-failed', onFailed, { once: true });
window.dispatchEvent(new Event('dotenv-save'));
} else if (draft?.environmentUid && draft?.variables) {
dispatch(saveEnvironment(draft.variables, draft.environmentUid, collection.uid))
.then(() => {
dispatch(clearEnvironmentsDraft({ collectionUid: collection.uid }));
@@ -263,6 +282,7 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
{showConfirmGlobalEnvironmentClose && tab.type === 'global-environment-settings' && (
<ConfirmCloseEnvironment
isGlobal={true}
isDotEnv={globalEnvironmentDraft?.environmentUid?.startsWith('dotenv:')}
onCancel={() => setShowConfirmGlobalEnvironmentClose(false)}
onCloseWithoutSave={() => {
dispatch(clearGlobalEnvironmentDraft());
@@ -271,7 +291,25 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
}}
onSaveAndClose={() => {
const draft = globalEnvironmentDraft;
if (draft?.environmentUid && draft?.variables) {
if (draft?.environmentUid?.startsWith('dotenv:')) {
const onSuccess = () => {
cleanup();
dispatch(clearGlobalEnvironmentDraft());
dispatch(closeTabs({ tabUids: [tab.uid] }));
setShowConfirmGlobalEnvironmentClose(false);
};
const onFailed = () => {
cleanup();
setShowConfirmGlobalEnvironmentClose(false);
};
const cleanup = () => {
window.removeEventListener('dotenv-save-complete', onSuccess);
window.removeEventListener('dotenv-save-failed', onFailed);
};
window.addEventListener('dotenv-save-complete', onSuccess, { once: true });
window.addEventListener('dotenv-save-failed', onFailed, { once: true });
window.dispatchEvent(new Event('dotenv-save'));
} else if (draft?.environmentUid && draft?.variables) {
dispatch(saveGlobalEnvironment({ variables: draft.variables, environmentUid: draft.environmentUid }))
.then(() => {
dispatch(clearGlobalEnvironmentDraft());

View File

@@ -13,7 +13,7 @@ import DotEnvFileDetails from 'components/Environments/DotEnvFileDetails';
import ColorBadge from 'components/ColorBadge';
import { isEqual } from 'lodash';
import { useDispatch, useSelector } from 'react-redux';
import { addGlobalEnvironment, renameGlobalEnvironment, selectGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
import { addGlobalEnvironment, renameGlobalEnvironment, selectGlobalEnvironment, setGlobalEnvironmentDraft, clearGlobalEnvironmentDraft } from 'providers/ReduxStore/slices/global-environments';
import {
saveWorkspaceDotEnvVariables,
saveWorkspaceDotEnvRaw,
@@ -72,9 +72,22 @@ const EnvironmentList = ({
const envUids = environments ? environments.map((env) => env.uid) : [];
const prevEnvUids = usePrevious(envUids);
const handleDotEnvModifiedChange = useCallback((modified) => {
setIsDotEnvModified(modified);
if (modified) {
dispatch(setGlobalEnvironmentDraft({
environmentUid: `dotenv:${selectedDotEnvFile}`,
variables: []
}));
} else {
dispatch(clearGlobalEnvironmentDraft());
}
}, [dispatch, selectedDotEnvFile]);
useEffect(() => {
if (dotEnvFiles.length === 0) {
setSelectedDotEnvFile(null);
handleDotEnvModifiedChange(false);
return;
}
@@ -422,7 +435,7 @@ const EnvironmentList = ({
dispatch(deleteWorkspaceDotEnvFile(workspace.uid, filename))
.then(() => {
toast.success(`${filename} file deleted!`);
setIsDotEnvModified(false);
handleDotEnvModifiedChange(false);
if (selectedDotEnvFile === filename) {
const remainingFiles = dotEnvFiles.filter((f) => f.filename !== filename);
if (remainingFiles.length > 0) {
@@ -465,7 +478,7 @@ const EnvironmentList = ({
onSave={handleSaveDotEnv}
onSaveRaw={handleSaveDotEnvRaw}
isModified={isDotEnvModified}
setIsModified={setIsDotEnvModified}
setIsModified={handleDotEnvModifiedChange}
dotEnvExists={selectedDotEnvData?.exists}
viewMode={dotEnvViewMode}
/>