Skip to content

Commit

Permalink
Alert user before closing the createItemModal form (#3317)
Browse files Browse the repository at this point in the history
The form data inside the `createItemModal` was getting lost when user
hit the `cancel` button. This could lead to a bad UX.

This fix will identifies if form data has changed from its initial
values before closing the modal. If yes, then it alerts the user for
confirmation. Otherwise, will close the modal.

Closes: #2822

Add cypress test for confirmation modal

Co-authored-by: Mike <mike@madebymike.com.au>
  • Loading branch information
singhArmani and MadeByMike committed Aug 4, 2020
1 parent 1a661e4 commit b0af7d5
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/purple-knives-cough.md
@@ -0,0 +1,5 @@
---
'@keystonejs/app-admin-ui': patch
---

Alerted the user before canceling the createItemModal form.
59 changes: 56 additions & 3 deletions packages/app-admin-ui/client/components/CreateItemModal.js
Expand Up @@ -16,6 +16,7 @@ import { useToasts } from 'react-toast-notifications';

import { Button, LoadingButton } from '@arch-ui/button';
import Drawer from '@arch-ui/drawer';
import Confirm from '@arch-ui/confirm';
import {
arrayToObject,
captureSuspensePromises,
Expand Down Expand Up @@ -50,6 +51,7 @@ const CreateItemModal = ({ prefillData = {}, onClose, onCreate, viewOnSave }) =>
const { list, closeCreateItemModal, isCreateItemModalOpen } = useList();

const [item, setItem] = useState(list.getInitialItemData({ prefill: prefillData }));
const [isConfirmOpen, setConfirmOpen] = useState(false);
const [validationErrors, setValidationErrors] = useState({});
const [validationWarnings, setValidationWarnings] = useState({});

Expand Down Expand Up @@ -129,16 +131,41 @@ const CreateItemModal = ({ prefillData = {}, onClose, onCreate, viewOnSave }) =>
}
});

const _onClose = () => {
if (loading) return;
// Identifies if the user has changed the initial data in the form.
const hasFormDataChanged = () => {
const data = arrayToObject(creatable, 'path');
let hasChanged = false;
const initialData = list.getInitialItemData({ prefill: prefillData });
const initialValues = getValues(data, initialData);
const currentValues = getValues(data, item);
for (const path of Object.keys(currentValues)) {
if (data[path].hasChanged(initialValues, currentValues)) {
hasChanged = true;
break;
}
}
return hasChanged;
};

const _createItemModalClose = () => {
closeCreateItemModal();
setItem(list.getInitialItemData({}));
const data = arrayToObject(creatable, 'path', field => field.serialize(item));
if (onClose) {
const data = arrayToObject(creatable, 'path', field => field.serialize(item));
onClose(data);
}
};

const _onClose = () => {
if (loading) return;
if (hasFormDataChanged()) {
// Ask for user confirmation before canceling.
setConfirmOpen(true);
return;
}
_createItemModalClose();
};

const _onKeyDown = event => {
if (event.defaultPrevented) return;
switch (event.key) {
Expand Down Expand Up @@ -244,10 +271,36 @@ const CreateItemModal = ({ prefillData = {}, onClose, onCreate, viewOnSave }) =>
));
}}
</Render>
<ConfirmModal
isOpen={isConfirmOpen}
onConfirm={() => {
setConfirmOpen(false);
_createItemModalClose();
}}
onCancel={() => setConfirmOpen(false)}
/>
</Suspense>
</div>
</Drawer>
);
};

const ConfirmModal = ({ isOpen, onConfirm, onCancel }) => {
return (
<Confirm isOpen={isOpen}>
<p style={{ marginTop: 0 }}>
All of your form data will be lost. Are you sure you want to cancel?
</p>
<footer>
<Button appearance="danger" variant="ghost" onClick={onConfirm}>
Ok
</Button>
<Button variant="subtle" onClick={onCancel}>
Cancel
</Button>
</footer>
</Confirm>
);
};

export default CreateItemModal;
17 changes: 17 additions & 0 deletions test-projects/basic/cypress/integration/create-buttons_spec.js
Expand Up @@ -75,4 +75,21 @@ describe('Home page', () => {
cy.contains('div', `Create ${text} Dialog`).should('not.exist');
});
});

it('Ensure Create Modal triggers a confirmation dialog when form data is filled, and user hits cancel button', () => {
cy.visit('/admin/users');

cy.get('#list-page-create-button').click({ force: true });
cy.get('#ks-input-name').type('Aman', {
force: true,
});
cy.contains('div', `Create User Dialog`)
.contains('button', 'Cancel')
.click({ force: true });

cy.get('div[role="alertdialog"]')
.contains('button', 'Cancel')
.click({ force: true });
cy.contains('div', `Create User Dialog`).should('exist');
});
});

0 comments on commit b0af7d5

Please sign in to comment.