Skip to content

Commit 9ec03e0

Browse files
authored
feat: Add confirmation dialog when closing Cloud Config edit parameter dialog without saving changes (#3247)
1 parent 64dbcf2 commit 9ec03e0

File tree

1 file changed

+82
-17
lines changed

1 file changed

+82
-17
lines changed

src/dashboard/Data/Config/ConfigDialog.react.js

Lines changed: 82 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ export default class ConfigDialog extends React.Component {
136136
optionsMenuOpen: false,
137137
error: null,
138138
syntaxColors: null,
139+
showDiscardConfirm: false,
139140
};
140141
if (props.param.length > 0) {
141142
let initialValue = props.value;
@@ -154,8 +155,17 @@ export default class ConfigDialog extends React.Component {
154155
confirmOverride: false,
155156
error: initialError,
156157
syntaxColors: null,
158+
showDiscardConfirm: false,
157159
};
158160
}
161+
162+
// Store initial values for change detection
163+
this.initialValues = {
164+
name: this.state.name,
165+
type: this.state.type,
166+
value: this.state.value,
167+
masterKeyOnly: this.state.masterKeyOnly,
168+
};
159169
}
160170

161171
componentDidMount() {
@@ -330,7 +340,47 @@ export default class ConfigDialog extends React.Component {
330340
return true;
331341
}
332342

343+
handleCancel() {
344+
if (this.state.showDiscardConfirm) {
345+
this.setState({ showDiscardConfirm: false });
346+
return;
347+
}
348+
if (this.hasChanges()) {
349+
this.setState({ showDiscardConfirm: true });
350+
} else {
351+
this.props.onCancel();
352+
}
353+
}
354+
355+
hasChanges() {
356+
if (
357+
this.state.name !== this.initialValues.name ||
358+
this.state.type !== this.initialValues.type ||
359+
this.state.masterKeyOnly !== this.initialValues.masterKeyOnly
360+
) {
361+
return true;
362+
}
363+
const currVal = this.state.value;
364+
const initVal = this.initialValues.value;
365+
if (currVal === initVal) {
366+
return false;
367+
}
368+
if (this.state.type === 'Date' && currVal && initVal) {
369+
return new Date(currVal).getTime() !== new Date(initVal).getTime();
370+
}
371+
if (this.state.type === 'GeoPoint' && currVal && initVal) {
372+
return currVal.latitude !== initVal.latitude || currVal.longitude !== initVal.longitude;
373+
}
374+
if (this.state.type === 'File' && currVal && initVal) {
375+
return currVal.url() !== initVal.url();
376+
}
377+
return true;
378+
}
379+
333380
submit() {
381+
if (this.state.showDiscardConfirm) {
382+
return;
383+
}
334384
this.props.onConfirm({
335385
name: this.state.name,
336386
type: this.state.type,
@@ -381,7 +431,7 @@ export default class ConfigDialog extends React.Component {
381431
// don't reset the editor value — preserve user edits.
382432
// Auto-enable the Diff toggle and reset the override confirmation.
383433
if (this.props.conflict && (!prevProps.conflict || this.props.value !== prevProps.value)) {
384-
this.setState({ showDiff: true, confirmOverride: false });
434+
this.setState({ showDiff: true, confirmOverride: false, showDiscardConfirm: false });
385435
return;
386436
}
387437

@@ -399,6 +449,8 @@ export default class ConfigDialog extends React.Component {
399449
error,
400450
masterKeyOnly: this.props.masterKeyOnly,
401451
});
452+
this.initialValues.value = updatedValue;
453+
this.initialValues.masterKeyOnly = this.props.masterKeyOnly;
402454
}
403455
}
404456

@@ -631,7 +683,7 @@ export default class ConfigDialog extends React.Component {
631683
)}
632684
</div>
633685
<div style={{ display: 'flex', gap: '12px' }}>
634-
<Button value="Cancel" onClick={this.props.onCancel} />
686+
<Button value="Cancel" onClick={this.handleCancel.bind(this)} />
635687
<Button
636688
primary={true}
637689
color="blue"
@@ -645,21 +697,34 @@ export default class ConfigDialog extends React.Component {
645697
);
646698

647699
return (
648-
<Modal
649-
type={Modal.Types.INFO}
650-
title={newParam ? 'New parameter' : 'Edit parameter'}
651-
icon="gear-solid"
652-
iconSize={30}
653-
subtitle={'Dynamically configure parts of your app'}
654-
customFooter={customFooter}
655-
disabled={!this.valid() || this.props.loading || (this.props.conflict && !this.state.confirmOverride)}
656-
onCancel={this.props.onCancel}
657-
onConfirm={this.submit.bind(this)}
658-
>
659-
<LoaderContainer loading={this.props.loading}>
660-
{dialogContent}
661-
</LoaderContainer>
662-
</Modal>
700+
<>
701+
<Modal
702+
type={Modal.Types.INFO}
703+
title={newParam ? 'New parameter' : 'Edit parameter'}
704+
icon="gear-solid"
705+
iconSize={30}
706+
subtitle={'Dynamically configure parts of your app'}
707+
customFooter={customFooter}
708+
disabled={!this.valid() || this.props.loading || (this.props.conflict && !this.state.confirmOverride)}
709+
onCancel={this.handleCancel.bind(this)}
710+
onConfirm={this.submit.bind(this)}
711+
>
712+
<LoaderContainer loading={this.props.loading}>
713+
{dialogContent}
714+
</LoaderContainer>
715+
</Modal>
716+
{this.state.showDiscardConfirm && (
717+
<Modal
718+
type={Modal.Types.DANGER}
719+
title="Discard unsaved changes?"
720+
subtitle="Your changes will be lost."
721+
confirmText="Discard"
722+
cancelText="Keep editing"
723+
onConfirm={this.props.onCancel}
724+
onCancel={() => this.setState({ showDiscardConfirm: false })}
725+
/>
726+
)}
727+
</>
663728
);
664729
}
665730
}

0 commit comments

Comments
 (0)