diff --git a/index.js b/index.js index 05e0222e..8856d601 100644 --- a/index.js +++ b/index.js @@ -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'; diff --git a/lib/ERMForm/ERMForm.js b/lib/ERMForm/ERMForm.js new file mode 100644 index 00000000..a6952bbe --- /dev/null +++ b/lib/ERMForm/ERMForm.js @@ -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 ( + + {(ctx) => ( + <> + ( + <> + {children(formProps)} + { + 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, + }} + /> + } + confirmLabel={} + heading={intl.formatMessage({ id: 'stripes-erm-components.areYouSure' })} + id="cancel-editing-confirmation" + message={} + onCancel={() => continueNavigation(ctx)} + onConfirm={closeModal} + open={openModal} + /> + + )} + + ); +}; + +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; diff --git a/lib/ERMForm/index.js b/lib/ERMForm/index.js new file mode 100644 index 00000000..2a84e03f --- /dev/null +++ b/lib/ERMForm/index.js @@ -0,0 +1 @@ +export { default } from './ERMForm'; diff --git a/lib/hooks/index.js b/lib/hooks/index.js index 571d0a0d..de52ae13 100644 --- a/lib/hooks/index.js +++ b/lib/hooks/index.js @@ -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'; diff --git a/lib/hooks/useErmForm.js b/lib/hooks/useErmForm.js new file mode 100644 index 00000000..19a72005 --- /dev/null +++ b/lib/hooks/useErmForm.js @@ -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; diff --git a/package.json b/package.json index 0cc5ecc8..0d510ea9 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/translations/stripes-erm-components/en.json b/translations/stripes-erm-components/en.json index a1e027e5..221dab10 100644 --- a/translations/stripes-erm-components/en.json +++ b/translations/stripes-erm-components/en.json @@ -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" }