Skip to content

Commit

Permalink
ERM-3041 Dashboard: the "unsaved changes" confirmation modal is missi…
Browse files Browse the repository at this point in the history
…ng (#663)

* ERM-3041 Dashboard: the "unsaved changes" confirmation modal is missing

* create first draft of useErmForm hook

* ERM-3041 Dashboard: the "unsaved changes" confirmation modal is missing

* add useHistory

* ERM-3041 Dashboard: the "unsaved changes" confirmation modal is missing

* move props from hook to ERMForm
* keep navigationCheck prop in hook and default to true

* ERM-3041 Dashboard: the "unsaved changes" confirmation modal is missing

* pass string instead of object to ConfirmationModals heading

* ERM-3041 Dashboard: the "unsaved changes" confirmation modal is missing

* add translations

* * make FormComponent a node

* Revert "* make FormComponent a node"

This reverts commit d58a208.

* * remove empty ghost file

* ERM-3041

* try useCallback

* fix: WIP Swap to ref based checks

Currently not reading properly on useEffect cleanup, got to work out why formSpyRef isn't properly read in unblock cleanup function

* feat: ERM Form

Split out ERM Form into a proper component, and leave the hook implementation as is. Cleans up implementation in our apps

---------

Co-authored-by: Ethan Freestone <[email protected]>
  • Loading branch information
CalamityC and EthanFreestone authored Mar 12, 2024
1 parent 1287952 commit 5ca9f56
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 1 deletion.
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export { default as DocumentFilter } from './lib/DocumentFilter';
export { default as DocumentsFieldArray } from './lib/DocumentsFieldArray';
export { default as DuplicateModal } from './lib/DuplicateModal';
export { default as EditCard } from './lib/EditCard';
export { default as ERMForm } from './lib/ERMForm';
export { default as Embargo } from './lib/Embargo';
export { default as EResourceType } from './lib/EResourceType';
export { default as FileUploader } from './lib/FileUploader';
Expand Down
97 changes: 97 additions & 0 deletions lib/ERMForm/ERMForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import PropTypes from 'prop-types';
import { Form as FinalForm, FormSpy } from 'react-final-form';
import createDecorator from 'final-form-focus';
import arrayMutators from 'final-form-arrays';
import { FormattedMessage, useIntl } from 'react-intl';
import { LastVisitedContext } from '@folio/stripes/core';
import { ConfirmationModal } from '@folio/stripes/components';

import { useErmForm } from '../hooks';

const focusOnErrors = createDecorator();

const ERMForm = ({
children,
decorators = [],
initialValues,
navigationCheck = true,
mutators = [],
onSubmit,
subscription = {},
...formOptions // Any other options we sould pass to final form
}) => {
const {
openModal,
continueNavigation,
closeModal,
formSpyRef,
_isMounted,
} = useErmForm({ navigationCheck });
const intl = useIntl();

return (
<LastVisitedContext.Consumer>
{(ctx) => (
<>
<FinalForm
{...formOptions}
decorators={[focusOnErrors, ...decorators]}
initialValues={initialValues}
mutators={{ ...mutators, ...arrayMutators }}
onSubmit={onSubmit}
render={(formProps) => (
<>
{children(formProps)}
<FormSpy
onChange={(state) => {
if (_isMounted.current) {
formSpyRef.current = state;
}
}}
subscription={{
dirty: true,
submitSucceeded: true,
invalid: true,
submitting: true,
}}
{...formProps}
/>
</>
)}
subscription={{
dirty: true,
submitSucceeded: true,
invalid: true,
submitting: true,
initialValues: true,
pristine: true,
...subscription,
}}
/>
<ConfirmationModal
cancelLabel={<FormattedMessage id="stripes-erm-components.closeWithoutSaving" />}
confirmLabel={<FormattedMessage id="stripes-erm-components.keepEditing" />}
heading={intl.formatMessage({ id: 'stripes-erm-components.areYouSure' })}
id="cancel-editing-confirmation"
message={<FormattedMessage id="stripes-erm-components.unsavedChanges" />}
onCancel={() => continueNavigation(ctx)}
onConfirm={closeModal}
open={openModal}
/>
</>
)}
</LastVisitedContext.Consumer>
);
};

ERMForm.propTypes = {
children: PropTypes.elementType.isRequired,
decorators: PropTypes.arrayOf(PropTypes.object),
initialValues: PropTypes.object,
navigationCheck: PropTypes.bool,
mutators: PropTypes.object,
onSubmit: PropTypes.func,
subscription: PropTypes.object,
};

export default ERMForm;
1 change: 1 addition & 0 deletions lib/ERMForm/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './ERMForm';
1 change: 1 addition & 0 deletions lib/hooks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ export { default as usePrevNextPagination } from './usePrevNextPagination';
export { default as usePrevious } from './usePrevious';
export { default as useSASQQIndex } from './useSASQQIndex';
export { default as useExportLogStream } from './useExportLogStream';
export { default as useErmForm } from './useErmForm';
72 changes: 72 additions & 0 deletions lib/hooks/useErmForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { useState, useEffect, useRef } from 'react';
import { useHistory } from 'react-router-dom';

const useErmForm = ({ navigationCheck = true } = {}) => {
const [openModal, setOpenModal] = useState(false);
const [nextLocation, setNextLocation] = useState(null);

const unblock = useRef();
const formSpyRef = useRef();
const _isMounted = useRef(false);

const history = useHistory();

useEffect(() => {
_isMounted.current = true;

const handleBlockedNavigation = (nextLoc) => {
const shouldPrompt =
!!formSpyRef.current &&
formSpyRef.current.dirty &&
!formSpyRef.current.submitSucceeded &&
!formSpyRef.current.submitting;

if (shouldPrompt) {
setOpenModal(true);
setNextLocation(nextLoc);
}

return !shouldPrompt;
};

// Set up the history.block listener
if (navigationCheck) {
unblock.current = history.block(handleBlockedNavigation);
}

// Clean up the history.block listener on unmount
return () => {
_isMounted.current = false;
if (unblock.current) {
unblock.current();
unblock.current = null;
}
};
}, [history, navigationCheck]);

const continueNavigation = (ctx) => {
const { pathname, search } = nextLocation;

ctx.cachePreviousUrl();
if (unblock.current) {
unblock.current();
unblock.current = null;
}
setOpenModal(false);
history.push(`${pathname}${search}`);
};

const closeModal = () => {
setOpenModal(false);
};

return {
openModal,
continueNavigation,
closeModal,
formSpyRef,
_isMounted,
};
};

export default useErmForm;
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"@k-int/stripes-kint-components": "^5.1.1",
"final-form": "^4.18.4",
"final-form-arrays": "^3.0.1",
"final-form-focus": "^1.1.2",
"lodash": "^4.17.4",
"prop-types": "^15.6.0",
"prop-types-extra": "^1.1.0",
Expand Down
7 changes: 6 additions & 1 deletion translations/stripes-erm-components/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -240,5 +240,10 @@
"numberOfTags": "{count, number} {count, plural, one {Tag} other {Tags}}",
"newTagCreated": "New tag created",
"no": "No",
"yes": "Yes"
"yes": "Yes",
"unsavedChanges": "There are unsaved changes",
"areYouSure": "Are you sure?",
"saveChanges": "Save Changes",
"closeWithoutSaving": "Close without saving",
"keepEditing": "Keep editing"
}

0 comments on commit 5ca9f56

Please sign in to comment.