diff --git a/src/components/common/TemplateFiller.tsx b/src/components/common/TemplateFiller.tsx index 35927a1..a1c7928 100644 --- a/src/components/common/TemplateFiller.tsx +++ b/src/components/common/TemplateFiller.tsx @@ -19,6 +19,7 @@ import { renderCustomField } from "./formHelpers"; import { CustomFieldValue } from "../../modules/manager/glossary/services/glossaryService"; import { useTranslation } from "react-i18next"; import { Namespaces } from "../../i18n/i18n"; +import { useFormWithGuard } from "./useFormRouter"; interface TemplateFillerProps { templateId?: GeneralIdRef; @@ -40,7 +41,7 @@ interface TemplateState { const TemplateFiller = forwardRef( ({ templateId, formInstanceId, onValidationChanged }, ref) => { - const form = useForm({ + const form = useFormWithGuard({ loaded: false, data: {}, errors: {}, diff --git a/src/components/common/useForm.ts b/src/components/common/useForm.ts index f0fe336..6311ac7 100644 --- a/src/components/common/useForm.ts +++ b/src/components/common/useForm.ts @@ -91,6 +91,9 @@ interface UseFormReturn { hasUnsavedChanges: () => boolean; markAsSaved: () => void; setupNavigationGuard: (onBlock?: (location: Location) => void) => () => void; + enableReactRouterGuard: () => void; + disableReactRouterGuard: () => void; + routerGuardEnabledRef: React.MutableRefObject; } export const useForm = (initialState: FormState): UseFormReturn => { @@ -581,6 +584,18 @@ export const useForm = (initialState: FormState): UseFormReturn => { initialDataRef.current = JSON.parse(JSON.stringify(state.data)); }, [state.data]); + // React Router navigation guard state + const routerGuardEnabledRef = useRef(true); + + // Setup React Router blocker for in-app navigation + const enableReactRouterGuard = useCallback((): void => { + routerGuardEnabledRef.current = true; + }, []); + + const disableReactRouterGuard = useCallback((): void => { + routerGuardEnabledRef.current = false; + }, []); + const setupNavigationGuard = useCallback( (onBlock?: (location: Location) => void): (() => void) => { const handleBeforeUnload = (e: BeforeUnloadEvent) => { @@ -633,6 +648,9 @@ export const useForm = (initialState: FormState): UseFormReturn => { hasUnsavedChanges, markAsSaved, setupNavigationGuard, + enableReactRouterGuard, + disableReactRouterGuard, + routerGuardEnabledRef, }; Object.defineProperty(api, "schema", { get: () => schemaRef.current, @@ -643,3 +661,34 @@ export const useForm = (initialState: FormState): UseFormReturn => { return api as UseFormReturn; }; + +/** + * Hook to enable React Router data router navigation blocking. + * + * Call this hook in your component to enable navigation guards for React Router's data router. + * This is optional and only needed if you're using RouterProvider (not BrowserRouter). + * The browserunload guard from useForm still works regardless. + * + * @param formApi The return value from useForm + * + * @example + * // In your component that is inside a data router: + * import { useBlocker } from "react-router-dom"; + * + * const MyEditForm = () => { + * const form = useForm(initialState); + * + * // Only call this if inside RouterProvider (data router) + * const blocker = useBlocker(({ currentLocation, nextLocation }) => + * form.routerGuardEnabledRef.current && + * form.hasUnsavedChanges() && + * currentLocation.pathname !== nextLocation.pathname + * ); + * + * return (...); + * } + */ +export const useFormRouterBlocker = (formApi: UseFormReturn): void => { + // This is a documentation function - actual implementation is done in the component + // See the example above for how to properly set up router blocking with useBlocker +}; diff --git a/src/components/common/useFormRouter.ts b/src/components/common/useFormRouter.ts new file mode 100644 index 0000000..6e66d21 --- /dev/null +++ b/src/components/common/useFormRouter.ts @@ -0,0 +1,159 @@ +import { useEffect, useRef } from "react"; +import { useLocation } from "react-router-dom"; +import { UseFormReturn, useForm, FormState } from "./useForm"; + +/** + * Hook for BrowserRouter navigation blocking with unsaved changes. + * + * Works with BrowserRouter (standard routing, not data router). + * Shows a confirmation dialog when user attempts to navigate away with unsaved changes. + * + * @param formApi - The return value from useForm() + * + * @example + * const form = useForm(initialState); + * useBrowserRouterFormGuard(form); + */ +export const useBrowserRouterFormGuard = (formApi: UseFormReturn): void => { + const location = useLocation(); + const { hasUnsavedChanges, routerGuardEnabledRef, disableReactRouterGuard } = + formApi; + const previousLocationRef = useRef(location); + const pendingNavigationRef = useRef(null); + + useEffect(() => { + // If navigation was attempted and we're at a new location + if ( + location.pathname !== previousLocationRef.current.pathname && + pendingNavigationRef.current + ) { + // Navigation succeeded, reset + previousLocationRef.current = location; + pendingNavigationRef.current = null; + return; + } + + // Check if we need to guard against navigation + if ( + hasUnsavedChanges() && + routerGuardEnabledRef.current && + pendingNavigationRef.current === null + ) { + // Store the current location for comparison + previousLocationRef.current = location; + } + }, [ + location, + hasUnsavedChanges, + routerGuardEnabledRef, + disableReactRouterGuard, + ]); + + // Intercept navigation attempts by wrapping navigate + useEffect(() => { + // Override the window's history behavior to catch navigation attempts + const originalPushState = window.history.pushState; + const originalReplaceState = window.history.replaceState; + + const handleNavigation = ( + method: (state: any, title: string, url?: string | null) => void, + state: any, + title: string, + url?: string | null, + ) => { + if ( + hasUnsavedChanges() && + routerGuardEnabledRef.current && + url && + url !== window.location.pathname + window.location.search + ) { + // Prompt user + const confirmed = window.confirm( + "You have unsaved changes. Do you want to leave without saving?", + ); + + if (confirmed) { + disableReactRouterGuard(); + method.call(window.history, state, title, url); + setTimeout(() => { + formApi.enableReactRouterGuard(); + }, 0); + } + } else { + method.call(window.history, state, title, url); + } + }; + + window.history.pushState = function (state, title, url) { + handleNavigation(originalPushState, state, title, url); + }; + + window.history.replaceState = function (state, title, url) { + handleNavigation(originalReplaceState, state, title, url); + }; + + return () => { + window.history.pushState = originalPushState; + window.history.replaceState = originalReplaceState; + }; + }, [ + hasUnsavedChanges, + routerGuardEnabledRef, + disableReactRouterGuard, + formApi, + ]); +}; + +/** + * Hook to enable React Router data router navigation blocking. + * + * Call this hook in your component to enable navigation guards for React Router's data router. + * This is optional and only needed if you're using RouterProvider (not BrowserRouter). + * The beforeunload guard from useForm still works regardless. + * + * @param formApi The return value from useForm + * + * @example + * // In your component that is inside a data router: + * import { useBlocker } from "react-router-dom"; + * + * const MyEditForm = () => { + * const form = useForm(initialState); + * + * // Only call this if inside RouterProvider (data router) + * const blocker = useBlocker(({ currentLocation, nextLocation }) => + * form.routerGuardEnabledRef.current && + * form.hasUnsavedChanges() && + * currentLocation.pathname !== nextLocation.pathname + * ); + * + * return (...); + * } + */ +export const useFormRouterBlocker = (formApi: UseFormReturn): void => { + // This is a documentation function - actual implementation is done in the component + // See the example above for how to properly set up router blocking with useBlocker +}; + +/** + * Combined hook that creates a form with automatic unsaved changes navigation guard. + * + * This is the recommended way to create forms with navigation protection in BrowserRouter apps. + * It combines useForm() and useBrowserRouterFormGuard() into a single call. + * + * @param initialState - The initial form state + * @returns The form API with navigation guard already set up + * + * @example + * // Instead of: + * const form = useForm(initialState); + * useBrowserRouterFormGuard(form); + * + * // Just use: + * const form = useFormWithGuard(initialState); + */ +export const useFormWithGuard = (initialState: FormState): UseFormReturn => { + const form = useForm(initialState); + useBrowserRouterFormGuard(form); + return form; +}; diff --git a/src/modules/frame/components/InternalLoginForm.tsx b/src/modules/frame/components/InternalLoginForm.tsx index b1d428f..f323ac0 100644 --- a/src/modules/frame/components/InternalLoginForm.tsx +++ b/src/modules/frame/components/InternalLoginForm.tsx @@ -4,7 +4,6 @@ import Joi from "joi"; import authentication from "../services/authenticationService"; import { InputType } from "../../../components/common/Input"; import { ButtonType } from "../../../components/common/Button"; -import { useForm } from "../../../components/common/useForm"; import { renderButton, renderError, @@ -12,6 +11,7 @@ import { } from "../../../components/common/formHelpers"; import { useTranslation } from "react-i18next"; import { Namespaces } from "../../../i18n/i18n"; +import { useFormWithGuard } from "../../../components/common/useFormRouter"; const InternalLoginForm: React.FC = () => { const { t } = useTranslation(Namespaces.Common); @@ -19,7 +19,7 @@ const InternalLoginForm: React.FC = () => { const [emailSent, setEmailSent] = useState(false); const passwordMaxLength = 255; - const form = useForm({ + const form = useFormWithGuard({ loaded: true, data: { username: "", diff --git a/src/modules/manager/customfields/customFieldDetails.tsx b/src/modules/manager/customfields/customFieldDetails.tsx index 4aa4b0f..e761dd1 100644 --- a/src/modules/manager/customfields/customFieldDetails.tsx +++ b/src/modules/manager/customfields/customFieldDetails.tsx @@ -28,6 +28,7 @@ import { SystemGlossaries, } from "../glossary/services/glossaryService"; import Loading from "../../../components/common/Loading"; +import { useFormWithGuard } from "../../../components/common/useFormRouter"; interface CustomFieldDetailsProps { editMode?: boolean; @@ -54,7 +55,7 @@ const CustomFieldDetails: React.FC = ({ const labelApply = t("Save"); const labelSave = t("SaveAndClose"); - const form = useForm({ + const form = useFormWithGuard({ loaded: false, data: { name: "", @@ -318,6 +319,8 @@ const CustomFieldDetails: React.FC = ({ } if (buttonName === "save") form.setState({ redirect: "/customfields" }); + + form.markAsSaved(); } catch (ex: any) { form.handleGeneralError(ex); } diff --git a/src/modules/manager/domains/components/AddUserToRole.tsx b/src/modules/manager/domains/components/AddUserToRole.tsx index e8511ab..5aeb408 100644 --- a/src/modules/manager/domains/components/AddUserToRole.tsx +++ b/src/modules/manager/domains/components/AddUserToRole.tsx @@ -13,6 +13,7 @@ import { renderButton, renderUserPicker, } from "../../../../components/common/formHelpers"; +import { useFormWithGuard } from "../../../../components/common/useFormRouter"; interface LocAddUserToRoleProps { isEditMode: boolean; @@ -29,7 +30,7 @@ const AddUserToRole: React.FC = ({ isEditMode }) => { const labelApply = t("Save"); const labelSave = t("SaveAndClose"); - const form = useForm({ + const form = useFormWithGuard({ loaded: true, data: { userId: undefined, @@ -57,6 +58,8 @@ const AddUserToRole: React.FC = ({ isEditMode }) => { form.setState({ redirect: `/domains/edit/${domainId}#securityRoles/${roleId}/users`, }); + + form.markAsSaved(); } } catch (ex: any) { form.handleGeneralError(ex); diff --git a/src/modules/manager/domains/components/EmailTemplateEditor.tsx b/src/modules/manager/domains/components/EmailTemplateEditor.tsx index 89741ea..edfebc4 100644 --- a/src/modules/manager/domains/components/EmailTemplateEditor.tsx +++ b/src/modules/manager/domains/components/EmailTemplateEditor.tsx @@ -14,6 +14,7 @@ import { renderInput, renderTemplateEditor, } from "../../../../components/common/formHelpers"; +import { useFormWithGuard } from "../../../../components/common/useFormRouter"; interface EmailTemplateEditorProps { domainId?: string; @@ -33,7 +34,7 @@ const EmailTemplateEditor: React.FC = ({ const labelApply = t("Save"); const labelSave = t("SaveAndClose"); - const form = useForm({ + const form = useFormWithGuard({ loaded: false, data: { currentMailType: undefined, diff --git a/src/modules/manager/domains/components/GeneralTab.tsx b/src/modules/manager/domains/components/GeneralTab.tsx index 5594196..a8d1765 100644 --- a/src/modules/manager/domains/components/GeneralTab.tsx +++ b/src/modules/manager/domains/components/GeneralTab.tsx @@ -15,6 +15,7 @@ import { renderInput, renderSsoProviderPicker, } from "../../../../components/common/formHelpers"; +import { useFormWithGuard } from "../../../../components/common/useFormRouter"; interface GeneralTabProps { isEditMode: boolean; @@ -34,7 +35,7 @@ const GeneralTab: React.FC = ({ isEditMode }) => { const labelApply = t("Save"); const labelSave = t("SaveAndClose"); - const form = useForm({ + const form = useFormWithGuard({ loaded: false, data: { name: "", @@ -117,6 +118,7 @@ const GeneralTab: React.FC = ({ isEditMode }) => { } if (buttonName === "save") form.setState({ redirect: "/domains" }); + form.markAsSaved(); } catch (ex: any) { form.handleGeneralError(ex); } diff --git a/src/modules/manager/domains/components/RolesDetails.tsx b/src/modules/manager/domains/components/RolesDetails.tsx index c8e73cb..71a55f7 100644 --- a/src/modules/manager/domains/components/RolesDetails.tsx +++ b/src/modules/manager/domains/components/RolesDetails.tsx @@ -14,6 +14,7 @@ import { renderButton, renderInput, } from "../../../../components/common/formHelpers"; +import { useFormWithGuard } from "../../../../components/common/useFormRouter"; interface RolesDetailsProps { isEditMode: boolean; @@ -30,7 +31,7 @@ const RolesDetails: React.FC = ({ isEditMode }) => { const labelApply = t("Save"); const labelSave = t("SaveAndClose"); - const form = useForm({ + const form = useFormWithGuard({ loaded: false, data: { name: "", diff --git a/src/modules/manager/forms/FormsDetails.tsx b/src/modules/manager/forms/FormsDetails.tsx index 3e8e3dd..ebbcedb 100644 --- a/src/modules/manager/forms/FormsDetails.tsx +++ b/src/modules/manager/forms/FormsDetails.tsx @@ -15,6 +15,7 @@ import { renderInput, renderTemplateEditor, } from "../../../components/common/formHelpers"; +import { useFormWithGuard } from "../../../components/common/useFormRouter"; const FormsDetails: React.FC<{ editMode?: boolean }> = ({ editMode = false, @@ -28,7 +29,7 @@ const FormsDetails: React.FC<{ editMode?: boolean }> = ({ const labelApply = t("Save"); const labelSave = t("SaveAndClose"); - const form = useForm({ + const form = useFormWithGuard({ loaded: false, data: { name: "", @@ -93,6 +94,7 @@ const FormsDetails: React.FC<{ editMode?: boolean }> = ({ } if (buttonName === "save") form.setState({ redirect: "/forms" }); + form.markAsSaved(); } catch (ex: any) { form.handleGeneralError(ex); } diff --git a/src/modules/manager/glossary/GlossariesDetails.tsx b/src/modules/manager/glossary/GlossariesDetails.tsx index 7e920a8..a134af5 100644 --- a/src/modules/manager/glossary/GlossariesDetails.tsx +++ b/src/modules/manager/glossary/GlossariesDetails.tsx @@ -21,6 +21,7 @@ import { renderCustomFields, renderCustomFieldsEditor, } from "../../../components/common/formHelpers"; +import { useFormWithGuard } from "../../../components/common/useFormRouter"; interface GlossariesDetailsProps { editMode?: boolean; @@ -38,7 +39,7 @@ const GlossariesDetails: React.FC = ({ const labelApply = t("Save"); const labelSave = t("SaveAndClose"); - const form = useForm({ + const form = useFormWithGuard({ loaded: false, data: { id: undefined, @@ -182,6 +183,7 @@ const GlossariesDetails: React.FC = ({ const navigateId = parentGlossary ? parentGlossary.id.toString() : ""; if (buttonName === "save") form.setState({ redirect: "/glossaries/" + navigateId }); + form.markAsSaved(); } catch (ex: any) { form.handleGeneralError(ex); } diff --git a/src/modules/manager/organisations/OrganisationsDetails.tsx b/src/modules/manager/organisations/OrganisationsDetails.tsx index ccff34e..ded407e 100644 --- a/src/modules/manager/organisations/OrganisationsDetails.tsx +++ b/src/modules/manager/organisations/OrganisationsDetails.tsx @@ -16,6 +16,7 @@ import { renderInput, renderSelect, } from "../../../components/common/formHelpers"; +import { useFormWithGuard } from "../../../components/common/useFormRouter"; const OrganisationsDetails: React.FC<{ editMode?: boolean }> = ({ editMode = false, @@ -36,7 +37,7 @@ const OrganisationsDetails: React.FC<{ editMode?: boolean }> = ({ { _id: "Blocked", name: t("Blocked") }, ]; - const form = useForm({ + const form = useFormWithGuard({ loaded: false, data: { name: "", @@ -112,6 +113,7 @@ const OrganisationsDetails: React.FC<{ editMode?: boolean }> = ({ } if (buttonName === "save") form.setState({ redirect: "/organisations" }); + form.markAsSaved(); } catch (ex: any) { form.handleGeneralError(ex); } diff --git a/src/modules/manager/sequence/SequenceDetails.tsx b/src/modules/manager/sequence/SequenceDetails.tsx index 4530c58..fb13d76 100644 --- a/src/modules/manager/sequence/SequenceDetails.tsx +++ b/src/modules/manager/sequence/SequenceDetails.tsx @@ -17,6 +17,7 @@ import sequenceService from "./services/sequenceService"; import Option from "../../../components/common/option"; import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef"; import Loading from "../../../components/common/Loading"; +import { useFormWithGuard } from "../../../components/common/useFormRouter"; interface SequenceDetailsProps { editMode?: boolean; @@ -37,7 +38,7 @@ const SequenceDetails: React.FC = ({ const labelApply = t("Save"); const labelSave = t("SaveAndClose"); - const form = useForm({ + const form = useFormWithGuard({ loaded: false, data: { name: "", @@ -131,6 +132,7 @@ const SequenceDetails: React.FC = ({ if (buttonName === "save") { form.setState({ redirect: "/sequence" }); } + form.markAsSaved(); } catch (ex: any) { form.handleGeneralError(ex); } diff --git a/src/modules/manager/sites/SiteDetails.tsx b/src/modules/manager/sites/SiteDetails.tsx index 3d81ed1..d687221 100644 --- a/src/modules/manager/sites/SiteDetails.tsx +++ b/src/modules/manager/sites/SiteDetails.tsx @@ -16,6 +16,7 @@ import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef"; import Option from "../../../components/common/option"; import siteService from "./services/sitessService"; import Loading from "../../../components/common/Loading"; +import { useFormWithGuard } from "../../../components/common/useFormRouter"; interface SiteDetailsProps { editMode?: boolean; @@ -35,7 +36,7 @@ const SiteDetails: React.FC = ({ editMode = false }) => { const labelApply = t("Save"); const labelSave = t("SaveAndClose"); - const form = useForm({ + const form = useFormWithGuard({ loaded: false, data: { name: "", @@ -118,6 +119,7 @@ const SiteDetails: React.FC = ({ editMode = false }) => { if (buttonName === "save") { form.setState({ redirect: "/site/" + organisationId }); } + form.markAsSaved(); } catch (ex: any) { form.handleGeneralError(ex); } diff --git a/src/modules/manager/specifications/SpecificationsDetails.tsx b/src/modules/manager/specifications/SpecificationsDetails.tsx index 7d558f5..f5af6ac 100644 --- a/src/modules/manager/specifications/SpecificationsDetails.tsx +++ b/src/modules/manager/specifications/SpecificationsDetails.tsx @@ -22,6 +22,7 @@ import { renderError, renderGlossaryPicker, } from "../../../components/common/formHelpers"; +import { useFormWithGuard } from "../../../components/common/useFormRouter"; interface SpecificationsDetailsProps { editMode?: boolean; @@ -44,7 +45,7 @@ const SpecificationsDetails: React.FC = ({ const labelApply = t("Save"); const labelSave = t("SaveAndClose"); - const form = useForm({ + const form = useFormWithGuard({ loaded: false, data: { name: "", @@ -193,6 +194,7 @@ const SpecificationsDetails: React.FC = ({ redirect: "/Specifications/" + organisationId + "/" + siteId, }); } + form.markAsSaved(); } catch (ex: any) { form.handleGeneralError(ex); } diff --git a/src/modules/manager/ssoManager/SsoProviderDetails.tsx b/src/modules/manager/ssoManager/SsoProviderDetails.tsx index 66ee94b..c51f4a2 100644 --- a/src/modules/manager/ssoManager/SsoProviderDetails.tsx +++ b/src/modules/manager/ssoManager/SsoProviderDetails.tsx @@ -4,7 +4,7 @@ import { Navigate, useParams, useLocation } from "react-router-dom"; import { toast } from "react-toastify"; import { useTranslation } from "react-i18next"; import { Namespaces } from "../../../i18n/i18n"; -import { useForm } from "../../../components/common/useForm"; +import { useFormWithGuard } from "../../../components/common/useFormRouter"; import { InputType } from "../../../components/common/Input"; import { renderInput, @@ -37,7 +37,7 @@ const SsoProviderDetails: React.FC = ({ const labelApply = t("Save"); const labelSave = t("SaveAndClose"); - const form = useForm({ + const form = useFormWithGuard({ loaded: false, data: { name: "", @@ -155,6 +155,8 @@ const SsoProviderDetails: React.FC = ({ if (buttonName === "save") { form.setState({ redirect: "/ssoManager" }); } + + form.markAsSaved(); } catch (ex: any) { form.handleGeneralError(ex); } @@ -286,7 +288,7 @@ const SsoProviderDetails: React.FC = ({ {editMode &&
Redirect URL: {redirectUrl}
} - {editMode && renderButton(labelApply, form.state.errors, "save")} + {editMode && renderButton(labelApply, form.state.errors, "apply")} {renderButton(labelSave, form.state.errors, "save")} diff --git a/src/modules/manager/users/components/GeneralTab.tsx b/src/modules/manager/users/components/GeneralTab.tsx index a090776..6f37dcb 100644 --- a/src/modules/manager/users/components/GeneralTab.tsx +++ b/src/modules/manager/users/components/GeneralTab.tsx @@ -17,6 +17,7 @@ import { renderError, renderDomainPicker, } from "../../../../components/common/formHelpers"; +import { useFormWithGuard } from "../../../../components/common/useFormRouter"; interface GeneralTabProps { isEditMode: boolean; @@ -35,7 +36,7 @@ const GeneralTab: React.FC = ({ isEditMode }) => { const labelApply = t("Save"); const labelSave = t("SaveAndClose"); - const form = useForm({ + const form = useFormWithGuard({ loaded: false, data: { firstName: "", @@ -131,6 +132,7 @@ const GeneralTab: React.FC = ({ isEditMode }) => { if (buttonName === "save") { form.setState({ redirect: "/users" }); } + form.markAsSaved(); } catch (ex: any) { form.handleGeneralError(ex); } diff --git a/src/modules/manager/workflowTemplates/WorkflowTemplateDetails.tsx b/src/modules/manager/workflowTemplates/WorkflowTemplateDetails.tsx index f8e189f..4e8f176 100644 --- a/src/modules/manager/workflowTemplates/WorkflowTemplateDetails.tsx +++ b/src/modules/manager/workflowTemplates/WorkflowTemplateDetails.tsx @@ -16,12 +16,12 @@ import { renderButton, renderError, } from "../../../components/common/formHelpers"; -import { useForm } from "../../../components/common/useForm"; import ErrorBlock from "../../../components/common/ErrorBlock"; import authentication from "../../frame/services/authenticationService"; import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef"; import { CustomFieldValue } from "../glossary/services/glossaryService"; import TasksTab from "./components/TasksTab"; +import { useFormWithGuard } from "../../../components/common/useFormRouter"; const WorkflowTemplateDetails: React.FC<{ editMode: boolean }> = ({ editMode, @@ -31,7 +31,7 @@ const WorkflowTemplateDetails: React.FC<{ editMode: boolean }> = ({ const [activeTab, setActiveTab] = React.useState("general"); // useForm promoted to the parent - const form = useForm({ + const form = useFormWithGuard({ loaded: false, data: { name: "", @@ -112,6 +112,8 @@ const WorkflowTemplateDetails: React.FC<{ editMode: boolean }> = ({ if (buttonName === "save") { form.setState({ ...form.state, redirect: "/workflowTemplates" }); } + + form.markAsSaved(); } catch (ex: any) { form.handleGeneralError(ex); } diff --git a/src/modules/manager/workflowTemplates/components/TaskList.tsx b/src/modules/manager/workflowTemplates/components/TaskList.tsx index 9066b36..1a33fc7 100644 --- a/src/modules/manager/workflowTemplates/components/TaskList.tsx +++ b/src/modules/manager/workflowTemplates/components/TaskList.tsx @@ -5,6 +5,7 @@ import { } from "../services/WorkflowTemplateService"; import AddTaskButton from "./AddTaskButton"; import { Namespaces } from "../../../../i18n/i18n"; +import { useTranslation } from "react-i18next"; interface TaskListProps { tasks: TaskDefinition[]; diff --git a/src/modules/profile/components/InternalProfile.tsx b/src/modules/profile/components/InternalProfile.tsx index f9e55db..7b9704a 100644 --- a/src/modules/profile/components/InternalProfile.tsx +++ b/src/modules/profile/components/InternalProfile.tsx @@ -15,6 +15,7 @@ import { renderToggle, renderDropSection, } from "../../../components/common/formHelpers"; +import { useFormWithGuard } from "../../../components/common/useFormRouter"; const InternalProfile: React.FC = () => { const { t } = useTranslation(Namespaces.Common); @@ -35,7 +36,7 @@ const InternalProfile: React.FC = () => { qrCodeImageUrl: "", }); - const form = useForm({ + const form = useFormWithGuard({ loaded: false, data: { firstName: "",