diff --git a/ui/src/app/App.js b/ui/src/app/App.js
index 82f67f8a0..c343c4eb4 100644
--- a/ui/src/app/App.js
+++ b/ui/src/app/App.js
@@ -1,37 +1,21 @@
import React from 'react';
import {
- BrowserRouter,
- Navigate,
- Routes,
- Route,
+ RouterProvider,
} from "react-router-dom";
-import { QueryParamProvider } from 'use-query-params';
import { Provider as HttpProvider } from 'use-http';
-import { ReactRouter6Adapter } from 'use-query-params/adapters/react-router-6';
import Button from 'react-bootstrap/Button';
import './App.scss';
import { I18nProvider } from './i18n/context/I18n.provider';
-import Footer from './core/components/Footer';
+import { UserProvider } from './core/user/UserContext';
+
import { get_cookie } from './core/utility/get_cookie';
-import Dashboard from './dashboard/view/Dashboard';
-import Header from './core/components/Header';
-import { UserProvider } from './core/user/UserContext';
-import { Metadata } from './metadata/Metadata';
-import { Attribute } from './metadata/Attribute';
-import { NotificationList } from './notifications/component/NotificationList';
-import { NewSource } from './metadata/new/NewSource';
-import { NewProvider } from './metadata/new/NewProvider';
-import { Filter } from './metadata/Filter';
import { Contention } from './metadata/contention/ContentionContext';
import { SessionModal } from './core/user/SessionModal';
-import { Roles } from './admin/Roles';
-import { Groups } from './admin/Groups';
-import { BASE_PATH } from './App.constant';
-import { ProtectRoute } from './core/components/ProtectRoute';
-import { IdpConfiguration } from './admin/IdpConfiguration';
-import { DynamicRegistration } from './dynamic-registration/DynamicRegistration';
+
+
+import { router } from './App.router';
// import { useBlocker } from './core/hooks/useBlocker';
@@ -67,55 +51,7 @@ function App() {
-
-
-
-
-
- } />
- } />
- } />
-
- } />
-
-
-
- } />
-
-
-
- } />
-
-
-
- } />
- } />
-
-
-
- } />
-
-
-
- } />
-
-
-
- } />
- } />
-
-
-
-
-
-
+
diff --git a/ui/src/app/App.root.js b/ui/src/app/App.root.js
new file mode 100644
index 000000000..01465efc1
--- /dev/null
+++ b/ui/src/app/App.root.js
@@ -0,0 +1,21 @@
+import React from 'react';
+
+import { QueryParamProvider } from 'use-query-params';
+import { ReactRouter6Adapter } from 'use-query-params/adapters/react-router-6';
+import Header from './core/components/Header';
+import Footer from './core/components/Footer';
+import { Outlet } from 'react-router';
+import { NotificationList } from './notifications/component/NotificationList';
+
+export function Root () {
+ return (
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/ui/src/app/App.router.js b/ui/src/app/App.router.js
new file mode 100644
index 000000000..ff65a19b3
--- /dev/null
+++ b/ui/src/app/App.router.js
@@ -0,0 +1,419 @@
+import { createBrowserRouter, Navigate } from "react-router-dom";
+import { BASE_PATH } from "./App.constant";
+import { Root } from "./App.root";
+import Dashboard from "./dashboard/view/Dashboard";
+import { SourcesTab } from "./dashboard/view/SourcesTab";
+import { Metadata } from './metadata/Metadata';
+import { Attribute } from './metadata/Attribute';
+import { NewSource } from './metadata/new/NewSource';
+import { NewProvider } from './metadata/new/NewProvider';
+import { Filter } from './metadata/Filter';
+import { Roles } from './admin/Roles';
+import { Groups } from './admin/Groups';
+import { ProtectRoute } from './core/components/ProtectRoute';
+import { DynamicRegistration } from './dynamic-registration/DynamicRegistration';
+import Spinner from './core/components/Spinner';
+import { ProvidersTab } from "./dashboard/view/ProvidersTab";
+import AdminTab from "./dashboard/view/AdminTab";
+import ActionsTab from "./dashboard/view/ActionsTab";
+import { DynamicRegistrationsTab } from "./dashboard/view/DynamicRegistrationsTab";
+import { ApproveSources } from "./dashboard/view/ApproveSources";
+import { EnableSources } from "./dashboard/view/EnableSources";
+import { ApproveRegistrations } from "./dashboard/view/ApproveRegistrations";
+import { EnableRegistrations } from "./dashboard/view/EnableRegistrations";
+import { UserAccess } from "./dashboard/view/UserAccess";
+
+import { MetadataOptions } from './metadata/view/MetadataOptions';
+import { MetadataDetail } from './metadata/component/MetadataDetail';
+import { MetadataHistory } from './metadata/view/MetadataHistory';
+
+import { MetadataXml } from './metadata/view/MetadataXml';
+import { MetadataComparison } from './metadata/view/MetadataComparison';
+import { MetadataVersion } from './metadata/view/MetadataVersion';
+import { MetadataEdit } from './metadata/view/MetadataEdit';
+import { MetadataRestore } from './metadata/view/MetadataRestore';
+import { MetadataConfirmRestore } from './metadata/view/MetadataConfirmRestore';
+
+import { MetadataSchema } from './metadata/hoc/MetadataSchema';
+import { MetadataWizard } from './metadata/view/MetadataWizard';
+import { MetadataCopy } from './metadata/view/MetadataCopy';
+import { MetadataUpload } from './metadata/view/MetadataUpload';
+import { MetadataSourceProtocolSelector } from './metadata/wizard/MetadataSourceProtocolSelector';
+import { GroupsProvider } from "./admin/hoc/GroupsProvider";
+import { GroupsList } from "./admin/container/GroupsList";
+import { Fragment } from "react";
+import { NewGroup } from "./admin/container/NewGroup";
+import { EditGroup } from "./admin/container/EditGroup";
+
+import { ConfigurationsProvider } from './admin/hoc/ConfigurationsProvider';
+import { ConfigurationList } from './admin/container/ConfigurationList';
+import { NewConfiguration } from './admin/container/NewConfiguration';
+import { EditConfiguration } from './admin/container/EditConfiguration';
+import { EditRole } from "./admin/container/EditRole";
+import { RolesProvider } from "./admin/hoc/RolesProvider";
+import { RoleList } from "./admin/container/RoleList";
+import { NewRole } from "./admin/container/NewRole";
+import { DynamicRegistrationCreate } from "./dynamic-registration/view/DynamicRegistrationCreate";
+import { DynamicRegistrationDetail } from "./dynamic-registration/view/DynamicRegistrationDetail";
+import { DynamicRegistrationEdit } from "./dynamic-registration/view/DynamicRegistrationEdit";
+import { MetadataAttributes } from "./metadata/hoc/MetadataAttributes";
+import { MetadataAttributeList } from "./metadata/view/MetadataAttributeList";
+import { NewAttribute } from "./metadata/new/NewAttribute";
+import { MetadataAttributeEdit } from "./metadata/view/MetadataAttributeEdit";
+import { MetadataAttributeBundles } from "./metadata/view/MetadataAttributeBundles";
+import { NewBundle } from "./metadata/new/NewBundle";
+import { MetadataAttributeBundleEdit } from "./metadata/view/MetadataAttributeBundleEdit";
+import { NewFilter } from "./metadata/new/NewFilter";
+import MetadataFilterSelector from "./metadata/hoc/MetadataFilterSelector";
+import { EditFilter } from "./metadata/view/EditFilter";
+import MetadataSelector from "./metadata/hoc/MetadataSelector";
+import { MetadataFilterList } from "./metadata/editor/MetadataFilterList";
+
+export const router = createBrowserRouter([
+ {
+ path: `/`,
+ element: ,
+ children: [
+ {
+ index: true,
+ element:
+ },
+ {
+ path: `/dashboard`,
+ element: ,
+ children: [
+ {
+ path: `metadata/manager/resolvers`,
+ element: ,
+ },
+ {
+ path: `metadata/manager/providers`,
+ element: ,
+ },
+ {
+ path: `dynamic-registration`,
+ element: ,
+ },
+ {
+ path: `admin/actions`,
+ element: ,
+ children: [
+ {
+ index: true,
+ element:
+ },
+ {
+ path: `approve-sources`,
+ element:
+ },
+ {
+ path: `approve-registrations`,
+ element:
+ },
+ {
+ path: `enable-sources`,
+ element:
+ },
+ {
+ path: `enable-registrations`,
+ element:
+ },
+ {
+ path: `useraccess`,
+ element:
+ }
+ ]
+ },
+ {
+ path: `admin/management`,
+ element:
+
+
+ },
+ {
+ index: true,
+ element:
+ }
+ ],
+ },
+ {
+ path: `metadata/:type/:id/`,
+ element: ,
+ children: [
+ {
+ path: `configuration/options`,
+ element:
+
+ ,
+ },
+ {
+ path: `configuration/xml`,
+ element:
+
+ ,
+ },
+ {
+ path: `configuration/history`,
+ element:
+
+ ,
+ },
+ {
+ path: `configuration/compare`,
+ element:
+
+ ,
+ },
+ {
+ path: `configuration/version/:versionId/options`,
+ element:
+
+ ,
+ },
+ {
+ path: `edit/:section`,
+ element: ,
+ },
+ {
+ path: `restore/:versionId/:section`,
+ element: ,
+ },
+ {
+ path: `restore/:versionId/:section/edit`,
+ element: ,
+ },
+ {
+ index: true,
+ element: ,
+ },
+ ]
+ },
+ {
+ path: 'metadata/source/new',
+ element: ,
+ children: [
+ {
+ path: `blank`,
+ element:
+ {(data, onRestart) =>
+
+
+
+ }
+ ,
+ },
+ {
+ path: `upload`,
+ element: ,
+ },
+ {
+ path: `copy`,
+ element:
+
+ ,
+ },
+ {
+ index: true,
+ element: ,
+ },
+ ]
+ },
+ {
+ path: `metadata/provider/new`,
+ element:
+
+
+ },
+ {
+ path: `metadata/provider/:id/filter`,
+ element: ,
+ children: [
+ {
+ path: `list`,
+ element:
+ {(entity) =>
+
+
+
+ }
+ ,
+ },
+ {
+ path: `new/:section`,
+ element:
+ },
+ {
+ path: `:filterId/edit/:section`,
+ element:
+
+
+ },
+ {
+ path: `new`,
+ element:
+ }
+ ]
+ },
+ {
+ path: `groups`,
+ element: ,
+ children: [
+ {
+ path: `list`,
+ element:
+ {(groups, onDelete) =>
+
+ }
+ ,
+ },
+ {
+ path: `new`,
+ element:
+ {(groups, onDelete, loading) =>
+ { loading ?
: }
+ }
+ ,
+ },
+ {
+ path: `:id/edit`,
+ element:
+ {(groups, onDelete, loading) =>
+ { loading ?
: }
+ }
+ ,
+ },
+ {
+ index: true,
+ element: ,
+ }
+ ]
+ },
+ {
+ path: `configurations`,
+ element: ,
+ children: [
+ {
+ path: `list`,
+ element:
+ {(configurations, onDelete) =>
+
+ }
+ ,
+ },
+ {
+ path: `new`,
+ element:
+ {(configurations) =>
+
+ }
+ ,
+ },
+ {
+ path: `:id/edit`,
+ element:
+ {(configurations) =>
+
+ }
+ ,
+ },
+ {
+ index: true,
+ element: ,
+ }
+ ]
+ },
+ {
+ path: `roles`,
+ element: ,
+ children: [
+ {
+ path: `list`,
+ element:
+ {(roles, onDelete) =>
+
+ }
+ ,
+ },
+ {
+ path: `new`,
+ element:,
+ },
+ {
+ path: `:id/edit`,
+ element: ,
+ },
+ {
+ index: true,
+ element: ,
+ }
+ ]
+ },
+ {
+ path: `dynamic-registration`,
+ element: ,
+ children: [
+ {
+ path: `new`,
+ element: ,
+ },
+ {
+ path: `:id`,
+ element: ,
+ },
+ {
+ path: `:id/edit`,
+ element: ,
+ }
+ ]
+ },
+ {
+ path: `metadata/attributes`,
+ element:
+
+ ,
+ children: [
+ {
+ path: `list`,
+ element:
+ {(entities, onDelete) =>
+
+ }
+ ,
+ },
+ {
+ path: `new`,
+ element:,
+ },
+ {
+ path: `:id/edit`,
+ element: ,
+ },
+ {
+ path: `bundles`,
+ element:
+ },
+ {
+ path: `bundles/new`,
+ element:
+ },
+ {
+ path: `bundles/:id/edit`,
+ element:
+ },
+ {
+ index: true,
+ element: ,
+ }
+ ]
+ }
+ ]
+ }
+], {
+ basename: BASE_PATH
+});
diff --git a/ui/src/app/admin/Groups.js b/ui/src/app/admin/Groups.js
index 10648eb43..f723114db 100644
--- a/ui/src/app/admin/Groups.js
+++ b/ui/src/app/admin/Groups.js
@@ -1,41 +1,8 @@
-import React, { Fragment } from 'react';
-import { Routes, Route, Navigate } from 'react-router-dom';
-import { GroupsProvider } from './hoc/GroupsProvider';
-import { NewGroup } from './container/NewGroup';
-import { EditGroup } from './container/EditGroup';
-import { GroupsList } from './container/GroupsList';
-import Spinner from '../core/components/Spinner';
+import React from 'react';
+import { Outlet } from 'react-router';
export function Groups() {
-
return (
- <>
-
-
- {(groups, onDelete) =>
-
- }
-
- } />
-
- {(groups, onDelete, loading) =>
- { loading ?
: }
- }
-
- } />
-
- {(groups, onDelete, loading) =>
- { loading ?
: }
- }
-
- } />
-
- } />
-
- >
+
);
}
\ No newline at end of file
diff --git a/ui/src/app/admin/IdpConfiguration.js b/ui/src/app/admin/IdpConfiguration.js
index 0be793f53..77417af38 100644
--- a/ui/src/app/admin/IdpConfiguration.js
+++ b/ui/src/app/admin/IdpConfiguration.js
@@ -1,41 +1,8 @@
import React from 'react';
-import { Routes, Route, Navigate } from 'react-router-dom';
-import { ConfigurationsProvider } from './hoc/ConfigurationsProvider';
-import { NewConfiguration } from './container/NewConfiguration';
-import { EditConfiguration } from './container/EditConfiguration';
-import { ConfigurationList } from './container/ConfigurationList';
+import { Outlet } from 'react-router-dom';
export function IdpConfiguration() {
-
return (
- <>
-
-
- {(configurations, onDelete) =>
-
- }
-
- } />
-
- {(configurations) =>
-
- }
-
-
- } />
-
- {(configurations) =>
-
- }
-
- } />
-
- } />
-
- >
+
);
}
\ No newline at end of file
diff --git a/ui/src/app/admin/Roles.js b/ui/src/app/admin/Roles.js
index f73e34ce9..7666fbe48 100644
--- a/ui/src/app/admin/Roles.js
+++ b/ui/src/app/admin/Roles.js
@@ -1,31 +1,8 @@
import React from 'react';
-import { Routes, Route, Navigate } from 'react-router-dom';
-import { RolesProvider } from './hoc/RolesProvider';
-import { NewRole } from './container/NewRole';
-import { EditRole } from './container/EditRole';
-import { RoleList } from './container/RoleList';
+import { Outlet } from 'react-router-dom';
export function Roles() {
return (
- <>
-
-
- {(roles, onDelete) =>
-
- }
-
- } />
-
- } />
-
- } />
-
- } />
-
- >
+
);
}
\ No newline at end of file
diff --git a/ui/src/app/admin/component/GroupForm.js b/ui/src/app/admin/component/GroupForm.js
index 1490091bc..806db59d4 100644
--- a/ui/src/app/admin/component/GroupForm.js
+++ b/ui/src/app/admin/component/GroupForm.js
@@ -9,10 +9,11 @@ import set from 'lodash/set';
import { useGroupUiSchema, useGroupUiValidator, useGroupParser, useGroupFormatter} from '../hooks';
import { FormContext, setFormDataAction, setFormErrorAction } from '../../form/FormManager';
-export function GroupForm ({group = {}, errors = [], context = {}, loading = false, schema, onSave, onCancel}) {
+export function GroupForm ({group = {}, errors = [], context = {}, loading = false, schema, onSave, onCancel, onTouched}) {
const { dispatch } = React.useContext(FormContext);
const onChange = ({formData, errors}) => {
+ onTouched(true);
dispatch(setFormDataAction(formData));
dispatch(setFormErrorAction(errors));
};
diff --git a/ui/src/app/admin/container/EditConfiguration.js b/ui/src/app/admin/container/EditConfiguration.js
index 30e653f39..05978d9e7 100644
--- a/ui/src/app/admin/container/EditConfiguration.js
+++ b/ui/src/app/admin/container/EditConfiguration.js
@@ -10,6 +10,7 @@ import { useTranslator } from '../../i18n/hooks';
import { PropertiesProvider } from '../hoc/PropertiesProvider';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
+import { usePrompt } from '../../core/hooks/usePrompt';
export function EditConfiguration({ configurations }) {
const navigate = useNavigate();
@@ -53,17 +54,14 @@ export function EditConfiguration({ configurations }) {
const gotoList = () => {
setBlocking(false);
- navigate(`/configurations`);
+ setTimeout(() => navigate(`/configurations`), 0);
};
+ const prompt = usePrompt(blocking, `message.unsaved-editor`);
+
return (
- {/*
- `message.unsaved-editor`
- }
- />*/}
+ {prompt}
diff --git a/ui/src/app/admin/container/EditGroup.js b/ui/src/app/admin/container/EditGroup.js
index ef5f1da5b..59ed06041 100644
--- a/ui/src/app/admin/container/EditGroup.js
+++ b/ui/src/app/admin/container/EditGroup.js
@@ -11,10 +11,9 @@ import { GroupProvider } from '../hoc/GroupProvider';
import { createNotificationAction, NotificationTypes } from '../../store/notifications/NotificationSlice';
import { useTranslator } from '../../i18n/hooks';
import { BASE_PATH } from '../../App.constant';
-
+import { usePrompt } from '../../core/hooks/usePrompt';
export function EditGroup({ groups }) {
-
const { id } = useParams();
const notifier = useDispatch();
@@ -25,6 +24,7 @@ export function EditGroup({ groups }) {
const { put, response, loading } = useGroups();
const [blocking, setBlocking] = React.useState(false);
+
async function save(metadata) {
let toast;
@@ -46,17 +46,14 @@ export function EditGroup({ groups }) {
const gotoDetail = () => {
setBlocking(false);
- navigate(`/groups`);
+ setTimeout(() => navigate(`/groups`), 0);
};
+ const prompt = usePrompt(blocking, `message.unsaved-editor`);
+
return (
- {/*
- `message.unsaved-editor`
- }
- />*/}
+ {prompt}
@@ -80,6 +77,7 @@ export function EditGroup({ groups }) {
schema={schema}
loading={loading}
onSave={(data) => save(data)}
+ onTouched={() => setBlocking(true)}
onCancel={() => cancel()} />}
}>}
diff --git a/ui/src/app/admin/container/EditRole.js b/ui/src/app/admin/container/EditRole.js
index d362731a1..08fc66d6a 100644
--- a/ui/src/app/admin/container/EditRole.js
+++ b/ui/src/app/admin/container/EditRole.js
@@ -13,6 +13,7 @@ import { createNotificationAction, NotificationTypes } from '../../store/notific
import { useTranslator } from '../../i18n/hooks';
import { BASE_PATH } from '../../App.constant';
import { useDispatch } from 'react-redux';
+import { usePrompt } from '../../core/hooks/usePrompt';
export function EditRole() {
@@ -47,17 +48,14 @@ export function EditRole() {
const gotoDetail = () => {
setBlocking(false);
- navigate(`/roles`);
+ setTimeout(() => navigate(`/roles`), 0);
};
+ const prompt = usePrompt(blocking, `message.unsaved-editor`);
+
return (
- {/*
- `message.unsaved-editor`
- }
- />*/}
+ {prompt}
diff --git a/ui/src/app/admin/container/NewConfiguration.js b/ui/src/app/admin/container/NewConfiguration.js
index fa17062ab..4e0f96a80 100644
--- a/ui/src/app/admin/container/NewConfiguration.js
+++ b/ui/src/app/admin/container/NewConfiguration.js
@@ -11,6 +11,7 @@ import { useTranslator } from '../../i18n/hooks';
import { BASE_PATH } from '../../App.constant';
import { PropertiesProvider } from '../hoc/PropertiesProvider';
import { useDispatch } from 'react-redux';
+import { usePrompt } from '../../core/hooks/usePrompt';
export function NewConfiguration({ configurations }) {
const navigate = useNavigate();
@@ -39,21 +40,18 @@ export function NewConfiguration({ configurations }) {
gotoList();
};
- const gotoList = (state = null) => {
+ const [configuration] = React.useState({});
+
+ const gotoList = () => {
setBlocking(false);
- navigate(`/configurations`);
+ setTimeout(() => navigate(`/configurations`), 0);
};
- const [configuration] = React.useState({});
+ const prompt = usePrompt(blocking, `message.unsaved-editor`);
return (
- {/*
- `message.unsaved-editor`
- }
- />*/}
+ {prompt}
diff --git a/ui/src/app/admin/container/NewGroup.js b/ui/src/app/admin/container/NewGroup.js
index 587f8f330..5050b9eec 100644
--- a/ui/src/app/admin/container/NewGroup.js
+++ b/ui/src/app/admin/container/NewGroup.js
@@ -11,6 +11,7 @@ import { createNotificationAction, NotificationTypes } from '../../store/notific
import { useTranslator } from '../../i18n/hooks';
import { BASE_PATH } from '../../App.constant';
import { useDispatch } from 'react-redux';
+import { usePrompt } from '../../core/hooks/usePrompt';
export function NewGroup({ groups }) {
const navigate = useNavigate();
@@ -39,19 +40,16 @@ export function NewGroup({ groups }) {
gotoDetail();
};
- const gotoDetail = (state = null) => {
+ const gotoDetail = () => {
setBlocking(false);
- navigate(`/groups`);
+ setTimeout(() => navigate(`/groups`), 0);
};
+ const prompt = usePrompt(blocking, `message.unsaved-editor`);
+
return (
- {/*
- `message.unsaved-editor`
- }
- />*/}
+ {prompt}
diff --git a/ui/src/app/admin/container/NewRole.js b/ui/src/app/admin/container/NewRole.js
index a818508ca..246410268 100644
--- a/ui/src/app/admin/container/NewRole.js
+++ b/ui/src/app/admin/container/NewRole.js
@@ -11,6 +11,7 @@ import { createNotificationAction, NotificationTypes } from '../../store/notific
import { useTranslator } from '../../i18n/hooks';
import { BASE_PATH } from '../../App.constant';
import { useDispatch } from 'react-redux';
+import { usePrompt } from '../../core/hooks/usePrompt';
export function NewRole() {
const navigate = useNavigate();
@@ -39,19 +40,16 @@ export function NewRole() {
gotoDetail();
};
- const gotoDetail = (state = null) => {
+ const gotoDetail = () => {
setBlocking(false);
- navigate(`/roles`);
+ setTimeout(() => navigate(`/roles`), 0);
};
+ const prompt = usePrompt(blocking, `message.unsaved-editor`);
+
return (
- {/*
- `message.unsaved-editor`
- }
- />*/}
+ {prompt}
diff --git a/ui/src/app/core/Prompt.js b/ui/src/app/core/Prompt.js
new file mode 100644
index 000000000..80e55a9d9
--- /dev/null
+++ b/ui/src/app/core/Prompt.js
@@ -0,0 +1,33 @@
+import React from 'react';
+
+import Modal from 'react-bootstrap/Modal';
+import Button from 'react-bootstrap/Button';
+import Translate from '../i18n/components/translate';
+
+export function Prompt({
+ show,
+ message,
+ title,
+ onConfirm,
+ onCancel,
+ confirmButtonText,
+ cancelButtonText,
+}) {
+
+ return (
+
+
+ {title}
+
+
+
+ {message}
+
+
+
+
+
+
+
+ );
+}
diff --git a/ui/src/app/core/components/UserConfirmation.js b/ui/src/app/core/components/UserConfirmation.js
deleted file mode 100644
index 16e7d3c10..000000000
--- a/ui/src/app/core/components/UserConfirmation.js
+++ /dev/null
@@ -1,55 +0,0 @@
-import React from 'react';
-import Modal from 'react-bootstrap/Modal';
-import Button from 'react-bootstrap/Button';
-import Translate from '../../i18n/components/translate';
-import { useNavigate } from 'react-router-dom';
-import { reload } from '../utility/window';
-
-export function UserConfirmation({children}) {
- const [confirm, setConfirm] = React.useState(false);
- const [confirmCallback, setConfirmCallback] = React.useState(null);
- const [message, setMessage] = React.useState('');
-
- function getConfirmation(message, callback) {
- setConfirmCallback(() => callback);
- setConfirm(true);
- setMessage(message);
- }
-
- return (<>{children(message, confirm, confirmCallback, setConfirm, getConfirmation)}>);
-}
-
-export function ConfirmWindow ({message, confirm, setConfirm, confirmCallback}) {
-
- const navigate = useNavigate();
-
- const allowTransition = () => {
- setConfirm(false);
- confirmCallback(true);
- /*if (history.location.pathname.includes('provider/new')) {
- reload();
- }*/
- }
-
- const blockTransition = () => {
- setConfirm(false);
- confirmCallback(false);
- }
-
- return (
-
-
- Are you sure?
-
-
-
- {message}
-
-
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/ui/src/app/core/hooks/useBlocker.tsx b/ui/src/app/core/hooks/useBlocker.tsx
deleted file mode 100644
index 0fd76a5fc..000000000
--- a/ui/src/app/core/hooks/useBlocker.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import * as React from "react";
-import { UNSAFE_NavigationContext as NavigationContext } from "react-router-dom";
-
-///console.log(router);
-
-export function useBlocker(blocker, when = true) {
- const { navigator } = React.useContext(NavigationContext);
-
- console.log(navigator);
-
- React.useEffect(() => {
- if (!when) return;
-
- const unblock = navigator.block((tx) => {
- const autoUnblockingTx = {
- ...tx,
- retry() {
- unblock();
- tx.retry();
- },
- };
-
- blocker(autoUnblockingTx);
- });
-
- return unblock;
- }, [navigator, blocker, when]);
-}
-
-export default function usePrompt(message, when = true) {
- const blocker = React.useCallback(
- (tx) => {
- if (window.confirm(message)) tx.retry();
- },
- [message]
- );
-
- useBlocker(blocker, when);
-}
diff --git a/ui/src/app/core/hooks/usePrompt.js b/ui/src/app/core/hooks/usePrompt.js
new file mode 100644
index 000000000..804706a53
--- /dev/null
+++ b/ui/src/app/core/hooks/usePrompt.js
@@ -0,0 +1,50 @@
+import React, { createElement } from 'react';
+
+import { unstable_useBlocker as useBlocker } from 'react-router';
+import { Prompt } from '../Prompt';
+
+export function usePrompt(when, message, {
+ title = 'Are you sure?',
+ confirmButtonText = `action.discard-changes`,
+ cancelButtonText = `action.cancel`
+} = {
+ title: 'Are you sure?',
+ confirmButtonText: `action.discard-changes`,
+ cancelButtonText: `action.cancel`
+}) {
+ let blocker = useBlocker(when);
+ const [show, setShow] = React.useState(false);
+
+ console.log(blocker);
+
+ React.useEffect(() => {
+ if (blocker.state === "blocked" && !when) {
+ blocker.reset();
+ }
+ }, [blocker, when]);
+
+ React.useEffect(() => {
+ if (blocker.state === "blocked") {
+ setShow(true);
+ }
+ }, [blocker]);
+
+ return createElement(
+ Prompt,
+ {
+ show,
+ message,
+ title,
+ confirmButtonText,
+ cancelButtonText,
+ onConfirm: () => {
+ setShow(false);
+ blocker.proceed();
+ },
+ onCancel: () => {
+ setShow(false);
+ blocker.reset();
+ }
+ });
+
+}
diff --git a/ui/src/app/dashboard/view/ActionsTab.js b/ui/src/app/dashboard/view/ActionsTab.js
index 5c716ce02..4706be423 100644
--- a/ui/src/app/dashboard/view/ActionsTab.js
+++ b/ui/src/app/dashboard/view/ActionsTab.js
@@ -4,19 +4,14 @@ import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Badge from 'react-bootstrap/Badge';
import Nav from 'react-bootstrap/Nav';
-import { Routes, Route, Navigate, NavLink } from 'react-router-dom';
+import { NavLink, Outlet } from 'react-router-dom';
-import { MetadataActions } from '../../admin/container/MetadataActions';
-import UserActions from '../../admin/container/UserActions';
-import Spinner from '../../core/components/Spinner';
import Translate from '../../i18n/components/translate';
-import SourceList from '../../metadata/domain/source/component/SourceList';
-import { ProtectRoute } from '../../core/components/ProtectRoute';
-import { DynamicRegistrationList } from '../../dynamic-registration/component/DynamicRegistrationList';
+
import { useGetNewUsersQuery } from '../../store/user/UserSlice';
import { useGetDisabledSourcesQuery, useGetUnapprovedSourcesQuery } from '../../store/metadata/SourceSlice';
-import { DynamicRegistrationActions } from '../../dynamic-registration/hoc/DynamicRegistrationActions';
+
import {
useGetDisabledRegistrationsQuery,
useGetUnapprovedRegistrationsQuery
@@ -25,9 +20,9 @@ import { useIsAdmin, useIsApprover } from '../../core/user/UserContext';
export function ActionsTab() {
- const {data: users, isFetching: loadingUsers, isError: usersError} = useGetNewUsersQuery();
- const {data: disabledSources, isFetching: loadingSources, isError: enableSourcesError} = useGetDisabledSourcesQuery();
- const {data: unApprovedSources, isFetching: loadingApprovals, isError: approveSourcesError} = useGetUnapprovedSourcesQuery();
+ const {data: users, isError: usersError} = useGetNewUsersQuery();
+ const {data: disabledSources, isError: enableSourcesError} = useGetDisabledSourcesQuery();
+ const {data: unApprovedSources, isError: approveSourcesError} = useGetUnapprovedSourcesQuery();
const {data: unApprovedRegistrations, isError: approveRegistrationsError } = useGetUnapprovedRegistrationsQuery();
const {data: disabledRegistrations, isError: enableRegistrationsError } = useGetDisabledRegistrationsQuery();
@@ -109,67 +104,7 @@ export function ActionsTab() {
-
- } />
-
- {({approve, remove}) =>
- remove(id)}
- onApprove={(s, e) => approve(s, e)}>
- {loadingApprovals &&
}
-
- }
-
- } />
-
- {({approve}) =>
- approve({id, approved})}>
- {loadingSources &&
}
-
- }
-
- } />
-
-
- {({enable, remove}) =>
- remove(id)}
- onEnable={(s, e) => enable(s, e)}>
- {loadingSources &&
}
-
- }
-
-
- } />
-
-
- {({enable}) =>
- {
- console.log(id, enabled)
- enable({ id, enabled });
- }}>
- {loadingSources &&
}
-
- }
-
-
- } />
-
-
- {loadingUsers &&
}
-
-
- } />
-
+
diff --git a/ui/src/app/dashboard/view/ApproveRegistrations.js b/ui/src/app/dashboard/view/ApproveRegistrations.js
new file mode 100644
index 000000000..fb536450d
--- /dev/null
+++ b/ui/src/app/dashboard/view/ApproveRegistrations.js
@@ -0,0 +1,22 @@
+import React from 'react';
+import Spinner from '../../core/components/Spinner';
+import { DynamicRegistrationList } from '../../dynamic-registration/component/DynamicRegistrationList';
+import { DynamicRegistrationActions } from '../../dynamic-registration/hoc/DynamicRegistrationActions';
+import { useGetUnapprovedRegistrationsQuery } from '../../store/dynamic-registration/DynamicRegistrationSlice';
+
+export function ApproveRegistrations () {
+
+ const {data: unApprovedRegistrations, isLoading } = useGetUnapprovedRegistrationsQuery();
+
+ return (
+
+ {({approve}) =>
+ approve({id, approved})}>
+ {isLoading &&
}
+
+ }
+
+ );
+}
\ No newline at end of file
diff --git a/ui/src/app/dashboard/view/ApproveSources.js b/ui/src/app/dashboard/view/ApproveSources.js
new file mode 100644
index 000000000..b8adc082d
--- /dev/null
+++ b/ui/src/app/dashboard/view/ApproveSources.js
@@ -0,0 +1,22 @@
+import React from 'react';
+import { MetadataActions } from '../../admin/container/MetadataActions';
+import Spinner from '../../core/components/Spinner';
+import SourceList from '../../metadata/domain/source/component/SourceList';
+import { useGetUnapprovedSourcesQuery } from '../../store/metadata/SourceSlice';
+
+export function ApproveSources () {
+
+ const {data: unApprovedSources, isFetching: loadingApprovals} = useGetUnapprovedSourcesQuery();
+
+ return (
+
+ {({approve, remove}) =>
+ remove(id)}
+ onApprove={(s, e) => approve(s, e)}>
+ {loadingApprovals &&
}
+
+ }
+
+ );
+}
\ No newline at end of file
diff --git a/ui/src/app/dashboard/view/Dashboard.js b/ui/src/app/dashboard/view/Dashboard.js
index 09f6ad8b6..e43753559 100644
--- a/ui/src/app/dashboard/view/Dashboard.js
+++ b/ui/src/app/dashboard/view/Dashboard.js
@@ -1,18 +1,12 @@
import React from 'react';
import Nav from 'react-bootstrap/Nav';
-import { Routes, Route, Navigate } from 'react-router-dom';
+import { Outlet } from 'react-router-dom';
import { NavLink } from 'react-router-dom';
import Translate from '../../i18n/components/translate';
-import { ProtectRoute } from '../../core/components/ProtectRoute';
-import { SourcesTab } from './SourcesTab';
-import { ProvidersTab } from './ProvidersTab';
-import { AdminTab } from './AdminTab';
-import { ActionsTab } from './ActionsTab';
import { useCurrentUserLoading, useIsAdmin, useIsApprover } from '../../core/user/UserContext';
-import { DynamicRegistrationsTab } from './DynamicRegistrationsTab';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
import Badge from 'react-bootstrap/Badge';
@@ -89,24 +83,7 @@ export function Dashboard () {
}
-
- } />
- } />
- } />
- } />
-
- } />
-
-
-
- } />
- } />
-
+
}
diff --git a/ui/src/app/dashboard/view/EnableRegistrations.js b/ui/src/app/dashboard/view/EnableRegistrations.js
new file mode 100644
index 000000000..0d0fe2814
--- /dev/null
+++ b/ui/src/app/dashboard/view/EnableRegistrations.js
@@ -0,0 +1,28 @@
+import React from 'react';
+import { ProtectRoute } from '../../core/components/ProtectRoute';
+import Spinner from '../../core/components/Spinner';
+import { DynamicRegistrationList } from '../../dynamic-registration/component/DynamicRegistrationList';
+import { DynamicRegistrationActions } from '../../dynamic-registration/hoc/DynamicRegistrationActions';
+import { useGetDisabledRegistrationsQuery } from '../../store/dynamic-registration/DynamicRegistrationSlice';
+
+export function EnableRegistrations () {
+
+ const {data: disabledRegistrations, isLoading } = useGetDisabledRegistrationsQuery();
+
+ return (
+
+
+ {({enable}) =>
+ {
+ console.log(id, enabled)
+ enable({ id, enabled });
+ }}>
+ {isLoading &&
}
+
+ }
+
+
+ );
+}
\ No newline at end of file
diff --git a/ui/src/app/dashboard/view/EnableSources.js b/ui/src/app/dashboard/view/EnableSources.js
new file mode 100644
index 000000000..27b2aaa53
--- /dev/null
+++ b/ui/src/app/dashboard/view/EnableSources.js
@@ -0,0 +1,25 @@
+import React from 'react';
+import { MetadataActions } from '../../admin/container/MetadataActions';
+import { ProtectRoute } from '../../core/components/ProtectRoute';
+import Spinner from '../../core/components/Spinner';
+import SourceList from '../../metadata/domain/source/component/SourceList';
+import { useGetDisabledSourcesQuery } from '../../store/metadata/SourceSlice';
+
+export function EnableSources () {
+
+ const {data: disabledSources, isFetching: loadingSources} = useGetDisabledSourcesQuery();
+
+ return (
+
+
+ {({enable, remove}) =>
+ remove(id)}
+ onEnable={(s, e) => enable(s, e)}>
+ {loadingSources &&
}
+
+ }
+
+
+ );
+}
\ No newline at end of file
diff --git a/ui/src/app/dashboard/view/UserAccess.js b/ui/src/app/dashboard/view/UserAccess.js
new file mode 100644
index 000000000..c26af6c89
--- /dev/null
+++ b/ui/src/app/dashboard/view/UserAccess.js
@@ -0,0 +1,18 @@
+import React from 'react';
+import UserActions from '../../admin/container/UserActions';
+import { ProtectRoute } from '../../core/components/ProtectRoute';
+import Spinner from '../../core/components/Spinner';
+import { useGetNewUsersQuery } from '../../store/user/UserSlice';
+
+export function UserAccess () {
+
+ const {data: users, isFetching: loadingUsers} = useGetNewUsersQuery();
+
+ return (
+
+
+ {loadingUsers &&
}
+
+
+ );
+}
\ No newline at end of file
diff --git a/ui/src/app/dynamic-registration/DynamicRegistration.js b/ui/src/app/dynamic-registration/DynamicRegistration.js
index dd3bd933e..fe32e5059 100644
--- a/ui/src/app/dynamic-registration/DynamicRegistration.js
+++ b/ui/src/app/dynamic-registration/DynamicRegistration.js
@@ -1,26 +1,11 @@
import React from 'react';
-import { Routes, Route } from 'react-router-dom';
-
-import { DynamicRegistrationDetail } from './view/DynamicRegistrationDetail';
-
-import { DynamicRegistrationEdit } from './view/DynamicRegistrationEdit';
-import { DynamicRegistrationCreate } from './view/DynamicRegistrationCreate';
+import { Outlet } from 'react-router-dom';
export function DynamicRegistration () {
return (
-
-
- } />
-
- } />
-
- } />
-
+
)
}
\ No newline at end of file
diff --git a/ui/src/app/dynamic-registration/view/DynamicRegistrationCreate.js b/ui/src/app/dynamic-registration/view/DynamicRegistrationCreate.js
index 822d7c781..1aaee37e9 100644
--- a/ui/src/app/dynamic-registration/view/DynamicRegistrationCreate.js
+++ b/ui/src/app/dynamic-registration/view/DynamicRegistrationCreate.js
@@ -8,6 +8,7 @@ import { DynamicRegistrationForm } from '../component/DynamicRegistrationForm';
import DynamicConfigurationDefinition from '../hoc/DynamicConfigurationDefinition';
import { useCreateDynamicRegistrationMutation } from '../../store/dynamic-registration/DynamicRegistrationSlice';
import Spinner from '../../core/components/Spinner';
+import { usePrompt } from '../../core/hooks/usePrompt';
export function DynamicRegistrationCreate () {
@@ -19,7 +20,7 @@ export function DynamicRegistrationCreate () {
const gotoDetail = React.useCallback(() => {
setBlocking(false);
- navigate(`/dashboard/dynamic-registration`);
+ setTimeout(() => navigate(`/dashboard/dynamic-registration`), 0);
}, [navigate]);
React.useEffect(() => {
@@ -30,14 +31,11 @@ export function DynamicRegistrationCreate () {
const [blocking, setBlocking] = React.useState(false);
+ const prompt = usePrompt(blocking, `message.unsaved-editor`);
+
return (
- {/*
- `message.unsaved-editor`
- }
- />*/}
+ {prompt}
diff --git a/ui/src/app/dynamic-registration/view/DynamicRegistrationEdit.js b/ui/src/app/dynamic-registration/view/DynamicRegistrationEdit.js
index 8198ee124..0170e45c6 100644
--- a/ui/src/app/dynamic-registration/view/DynamicRegistrationEdit.js
+++ b/ui/src/app/dynamic-registration/view/DynamicRegistrationEdit.js
@@ -8,6 +8,7 @@ import { DynamicRegistrationForm } from '../component/DynamicRegistrationForm';
import DynamicConfigurationDefinition from '../hoc/DynamicConfigurationDefinition';
import { useSelectDynamicRegistrationQuery, useUpdateDynamicRegistrationMutation } from '../../store/dynamic-registration/DynamicRegistrationSlice';
import Spinner from '../../core/components/Spinner';
+import { usePrompt } from '../../core/hooks/usePrompt';
export function DynamicRegistrationEdit () {
@@ -23,7 +24,7 @@ export function DynamicRegistrationEdit () {
const gotoDetail = useCallback(() => {
setBlocking(false);
- navigate(`/dynamic-registration/${id}`);
+ setTimeout(() => navigate(`/dynamic-registration/${id}`), 0);
}, [id, navigate]);
const [blocking, setBlocking] = React.useState(false);
@@ -34,16 +35,13 @@ export function DynamicRegistrationEdit () {
}
}, [isSuccess, gotoDetail]);
+ const prompt = usePrompt(blocking, `message.unsaved-editor`);
+
return (
{detail &&
- {/*
- `message.unsaved-editor`
- }
- />*/}
+ {prompt}
diff --git a/ui/src/app/metadata/Attribute.js b/ui/src/app/metadata/Attribute.js
index 3026dedaa..693c686fa 100644
--- a/ui/src/app/metadata/Attribute.js
+++ b/ui/src/app/metadata/Attribute.js
@@ -1,42 +1,9 @@
import React from 'react';
-import { Routes, Route, Navigate } from 'react-router-dom';
-import { MetadataAttributes } from './hoc/MetadataAttributes';
-import { NewAttribute } from './new/NewAttribute';
-import { MetadataAttributeEdit } from './view/MetadataAttributeEdit';
-import { MetadataAttributeList } from './view/MetadataAttributeList';
-import { MetadataAttributeBundles } from './view/MetadataAttributeBundles';
-import { NewBundle } from './new/NewBundle';
-import { MetadataAttributeBundleEdit } from './view/MetadataAttributeBundleEdit';
+import { Outlet } from 'react-router-dom';
export function Attribute() {
return (
-
-
- {(entities, onDelete) =>
-
- }
-
- } />
-
- } />
-
- } />
-
- } />
-
- } />
-
- } />
-
- } />
-
+
);
}
\ No newline at end of file
diff --git a/ui/src/app/metadata/Filter.js b/ui/src/app/metadata/Filter.js
index 881873d1e..db63b22b0 100644
--- a/ui/src/app/metadata/Filter.js
+++ b/ui/src/app/metadata/Filter.js
@@ -1,36 +1,8 @@
import React from 'react';
-import { Routes, Route, Navigate } from 'react-router-dom';
-import { MetadataFilterList } from './editor/MetadataFilterList';
-import MetadataFilterSelector from './hoc/MetadataFilterSelector';
-import MetadataSchema from './hoc/MetadataSchema';
-import MetadataSelector from './hoc/MetadataSelector';
-import { NewFilter } from './new/NewFilter';
-import { EditFilter } from './view/EditFilter';
+import { Outlet } from 'react-router-dom';
export function Filter() {
-
return (
-
-
- {(entity) =>
-
-
-
- }
-
- } />
-
- } />
-
-
-
- } />
-
- } />
-
+
);
}
\ No newline at end of file
diff --git a/ui/src/app/metadata/Metadata.js b/ui/src/app/metadata/Metadata.js
index 426f99f07..4f0ee2428 100644
--- a/ui/src/app/metadata/Metadata.js
+++ b/ui/src/app/metadata/Metadata.js
@@ -1,17 +1,9 @@
import React from 'react';
-import { Routes, Route, Navigate } from 'react-router-dom';
-import { MetadataOptions } from './view/MetadataOptions';
-import { MetadataDetail } from './component/MetadataDetail';
-import { MetadataHistory } from './view/MetadataHistory';
+import { Outlet } from 'react-router-dom';
import { MetadataSelector } from './hoc/MetadataSelector';
import { MetadataSchema } from './hoc/MetadataSchema';
import { MetadataXmlLoader } from './hoc/MetadataXmlLoader';
-import { MetadataXml } from './view/MetadataXml';
-import { MetadataComparison } from './view/MetadataComparison';
-import { MetadataVersion } from './view/MetadataVersion';
-import { MetadataEdit } from './view/MetadataEdit';
-import { MetadataRestore } from './view/MetadataRestore';
-import { MetadataConfirmRestore } from './view/MetadataConfirmRestore';
+
export function Metadata () {
@@ -20,45 +12,7 @@ export function Metadata () {
{(entity, reload) =>
-
-
-
-
- } />
-
-
-
- } />
-
-
-
- } />
-
-
-
- } />
-
-
-
- } />
-
- } />
-
- } />
-
- } />
- {
- } />}
-
+
}
diff --git a/ui/src/app/metadata/editor/MetadataEditor.js b/ui/src/app/metadata/editor/MetadataEditor.js
index cf488cbe7..4630dd970 100644
--- a/ui/src/app/metadata/editor/MetadataEditor.js
+++ b/ui/src/app/metadata/editor/MetadataEditor.js
@@ -21,6 +21,7 @@ import { checkChanges } from '../hooks/utility';
import { createNotificationAction, NotificationTypes } from '../../store/notifications/NotificationSlice';
import { useUserGroup } from '../../core/user/UserContext';
import { useDispatch } from 'react-redux';
+import { usePrompt } from '../../core/hooks/usePrompt';
export function MetadataEditor ({ restore, current, reload }) {
@@ -72,7 +73,7 @@ export function MetadataEditor ({ restore, current, reload }) {
const gotoDetail = (state = null) => {
setBlocking(false);
- setTimeout(() => navigate(`/metadata/${type}/${id}`) );
+ setTimeout(() => navigate(`/metadata/${type}/${id}`), 0);
};
const onNavigate = (path) => {
@@ -92,14 +93,11 @@ export function MetadataEditor ({ restore, current, reload }) {
const canFilter = restore ? false : FilterableProviders.indexOf(definition.type) > -1;
+ const prompt = usePrompt(blocking, `message.unsaved-editor`);
+
return (
- {/*
- `message.unsaved-editor`
- }
- />*/}
+ {prompt}
diff --git a/ui/src/app/metadata/editor/MetadataFilterList.js b/ui/src/app/metadata/editor/MetadataFilterList.js
index a888a6efc..48713f850 100644
--- a/ui/src/app/metadata/editor/MetadataFilterList.js
+++ b/ui/src/app/metadata/editor/MetadataFilterList.js
@@ -1,7 +1,7 @@
import React from 'react';
import { faArrowLeft, faCogs, faPlus } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { generatePath, matchPath, useNavigate, useParams } from 'react-router-dom';
+import { generatePath, useNavigate, useParams } from 'react-router-dom';
import Translate from '../../i18n/components/translate';
import { MetadataDefinitionContext, MetadataSchemaContext } from '../hoc/MetadataSchema';
diff --git a/ui/src/app/metadata/new/NewAttribute.js b/ui/src/app/metadata/new/NewAttribute.js
index f7bee6e03..d3b88d6b4 100644
--- a/ui/src/app/metadata/new/NewAttribute.js
+++ b/ui/src/app/metadata/new/NewAttribute.js
@@ -13,6 +13,7 @@ import MetadataSchema from '../hoc/MetadataSchema';
import { MetadataForm } from '../hoc/MetadataFormContext';
import { createNotificationAction, NotificationTypes } from '../../store/notifications/NotificationSlice';
import { useDispatch } from 'react-redux';
+import { usePrompt } from '../../core/hooks/usePrompt';
export function NewAttribute() {
const navigate = useNavigate();
@@ -45,17 +46,14 @@ export function NewAttribute() {
const gotoDetail = (state = null) => {
setBlocking(false);
- navigate(`/metadata/attributes`);
+ setTimeout(() => navigate(`/metadata/attributes`), 0);
};
+ const prompt = usePrompt(blocking, `message.unsaved-editor`);
+
return (
- {/*
- `message.unsaved-editor`
- }
- />*/}
+ {prompt}
diff --git a/ui/src/app/metadata/new/NewBundle.js b/ui/src/app/metadata/new/NewBundle.js
index 955d0352e..141f7e7b9 100644
--- a/ui/src/app/metadata/new/NewBundle.js
+++ b/ui/src/app/metadata/new/NewBundle.js
@@ -11,6 +11,7 @@ import MetadataSchema from '../hoc/MetadataSchema';
import { MetadataForm } from '../hoc/MetadataFormContext';
import { AttributeBundleEditor } from '../editor/AttributeBundleEditor';
import { AttributeBundleApi } from '../hoc/attribute/AttributeBundleApi';
+import { usePrompt } from '../../core/hooks/usePrompt';
export function NewBundle() {
const navigate = useNavigate();
@@ -25,19 +26,16 @@ export function NewBundle() {
const gotoDetail = (state = null) => {
setBlocking(false);
- navigate(`/metadata/attributes/bundles`);
+ setTimeout(() => navigate(`/metadata/attributes/bundles`), 0);
};
+ const prompt = usePrompt(blocking, `message.unsaved-editor`);
+
return (
{(load, find, create, update, remove, loading) =>
- {/*
- `message.unsaved-editor`
- }
- />*/}
+ {prompt}
diff --git a/ui/src/app/metadata/new/NewFilter.js b/ui/src/app/metadata/new/NewFilter.js
index bf5c064af..0a0fe1502 100644
--- a/ui/src/app/metadata/new/NewFilter.js
+++ b/ui/src/app/metadata/new/NewFilter.js
@@ -11,6 +11,7 @@ import { useMetadataFilters, useMetadataFilterTypes } from '../hooks/api';
import { MetadataFilterTypeSelector } from '../wizard/MetadataFilterTypeSelector';
import { createNotificationAction, NotificationTypes } from '../../store/notifications/NotificationSlice';
import { useDispatch } from 'react-redux';
+import { usePrompt } from '../../core/hooks/usePrompt';
export function NewFilter() {
@@ -51,17 +52,14 @@ export function NewFilter() {
setTimeout(() => {
navigate(path);
setBlocking(resetBlock);
- });
+ }, 0);
};
+ const prompt = usePrompt(blocking, `message.unsaved-editor`);
+
return (
- {/*
- `message.unsaved-editor`
- }
- />*/}
+ {prompt}
diff --git a/ui/src/app/metadata/new/NewSource.js b/ui/src/app/metadata/new/NewSource.js
index e98821f46..8ea5d57cb 100644
--- a/ui/src/app/metadata/new/NewSource.js
+++ b/ui/src/app/metadata/new/NewSource.js
@@ -1,21 +1,16 @@
-import React from 'react';
-import { NavLink, Navigate, Route, Routes } from 'react-router-dom';
+import React, { createContext } from 'react';
+import { NavLink, Outlet } from 'react-router-dom';
import Translate from '../../i18n/components/translate';
-import { MetadataSchema } from '../hoc/MetadataSchema';
-import { MetadataWizard } from '../view/MetadataWizard';
-import { MetadataCopy } from '../view/MetadataCopy';
-import { MetadataUpload } from '../view/MetadataUpload';
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCopy, faLink, faPlusSquare } from '@fortawesome/free-solid-svg-icons';
-import { MetadataSourceProtocolSelector } from '../wizard/MetadataSourceProtocolSelector';
-import { useMetadataSourceProtocols } from '../hooks/api';
+
+export const ShowNavContext = createContext(true);
export function NewSource() {
const [showNav, setShowNav] = React.useState(true);
- const protocols = useMetadataSourceProtocols();
-
const btnClasses = 'btn btn-lg btn-block d-flex flex-column justify-content-center align-items-center';
return (
@@ -82,36 +77,9 @@ export function NewSource() {
>}
-
-
- {(data, onRestart) =>
-
- { setShowNav(s) }}
- onCallback={onRestart}
- data={{
- protocol: data.protocol,
- serviceProviderName: data.serviceProviderName,
- entityId: data.entityId
- }} />
-
- }
-
-
- } />
-
- } />
-
- { setShowNav(s) } } />
-
- } />
-
- } />
-
+
+
+
diff --git a/ui/src/app/metadata/view/EditFilter.js b/ui/src/app/metadata/view/EditFilter.js
index 32838a785..31ee00962 100644
--- a/ui/src/app/metadata/view/EditFilter.js
+++ b/ui/src/app/metadata/view/EditFilter.js
@@ -13,6 +13,7 @@ import { useMetadataFilterObject } from '../hoc/MetadataFilterSelector';
import API_BASE_PATH from '../../App.constant';
import { createNotificationAction, NotificationTypes } from '../../store/notifications/NotificationSlice';
import { useDispatch } from 'react-redux';
+import { usePrompt } from '../../core/hooks/usePrompt';
export function EditFilter() {
@@ -52,17 +53,14 @@ export function EditFilter() {
const gotoDetail = (state = null) => {
setBlocking(false);
- navigate(`/metadata/provider/${id}`);
+ setTimeout(() => navigate(`/metadata/provider/${id}`), 0);
};
+ const prompt = usePrompt(blocking, `message.unsaved-editor`);
+
return (
- {/*
- `message.unsaved-editor`
- }
- />*/}
+ {prompt}
Edit filter
diff --git a/ui/src/app/metadata/view/MetadataAttributeBundleEdit.js b/ui/src/app/metadata/view/MetadataAttributeBundleEdit.js
index a08082395..3f6a31119 100644
--- a/ui/src/app/metadata/view/MetadataAttributeBundleEdit.js
+++ b/ui/src/app/metadata/view/MetadataAttributeBundleEdit.js
@@ -12,6 +12,7 @@ import MetadataSchema from '../hoc/MetadataSchema';
import { MetadataForm } from '../hoc/MetadataFormContext';
import { AttributeBundleSelector } from '../hoc/attribute/AttributeBundleSelector';
import { AttributeBundleApi } from '../hoc/attribute/AttributeBundleApi';
+import { usePrompt } from '../../core/hooks/usePrompt';
export function MetadataAttributeBundleEdit() {
const { id } = useParams();
@@ -27,20 +28,17 @@ export function MetadataAttributeBundleEdit() {
const gotoDetail = (state = null) => {
setBlocking(false);
- navigate(`/metadata/attributes/bundles`);
+ setTimeout(() => navigate(`/metadata/attributes/bundles`), 0);
};
+ const prompt = usePrompt(blocking, `message.unsaved-editor`);
+
return (
{(load, find, create, update, remove, loading) =>
{(bundle) =>
- {/*
- `message.unsaved-editor`
- }
- />*/}
+ {prompt}
diff --git a/ui/src/app/metadata/view/MetadataAttributeEdit.js b/ui/src/app/metadata/view/MetadataAttributeEdit.js
index 5ef0b14be..2b6967d61 100644
--- a/ui/src/app/metadata/view/MetadataAttributeEdit.js
+++ b/ui/src/app/metadata/view/MetadataAttributeEdit.js
@@ -13,6 +13,7 @@ import MetadataSchema from '../hoc/MetadataSchema';
import { MetadataForm } from '../hoc/MetadataFormContext';
import { createNotificationAction, NotificationTypes } from '../../store/notifications/NotificationSlice';
import { useDispatch } from 'react-redux';
+import { usePrompt } from '../../core/hooks/usePrompt';
export function MetadataAttributeEdit() {
const { id } = useParams();
@@ -55,7 +56,7 @@ export function MetadataAttributeEdit() {
const gotoDetail = (state = null) => {
setBlocking(false);
- navigate(`/metadata/attributes`);
+ setTimeout(() => navigate(`/metadata/attributes`), 0);
};
const [attribute, setAttribute] = React.useState();
@@ -65,14 +66,11 @@ export function MetadataAttributeEdit() {
loadAttribute();
}, []);
+ const prompt = usePrompt(blocking, `message.unsaved-editor`);
+
return (
- {/*
- `message.unsaved-editor`
- }
- />*/}
+ {prompt}
diff --git a/ui/src/app/metadata/view/MetadataCopy.js b/ui/src/app/metadata/view/MetadataCopy.js
index 5a0d44673..e1855959b 100644
--- a/ui/src/app/metadata/view/MetadataCopy.js
+++ b/ui/src/app/metadata/view/MetadataCopy.js
@@ -5,12 +5,15 @@ import { useMetadataEntity } from '../hooks/api';
import { useNavigate } from 'react-router-dom';
import { createNotificationAction, NotificationTypes } from '../../store/notifications/NotificationSlice';
import { useDispatch } from 'react-redux';
+import { ShowNavContext } from '../new/NewSource';
-export function MetadataCopy ({ onShowNav }) {
+export function MetadataCopy () {
const { post, response, loading } = useMetadataEntity('source');
const navigate = useNavigate();
+ const { setShowNav } = React.useContext(ShowNavContext);
+
const dispatch = useDispatch();
const [copy, setCopy] = React.useState({
@@ -24,12 +27,12 @@ export function MetadataCopy ({ onShowNav }) {
const next = (data) => {
setCopy(data);
setConfirm(true);
- onShowNav(false);
+ setShowNav(false);
};
const back = (data) => {
setConfirm(false);
- onShowNav(true);
+ setShowNav(true);
};
async function save (data) {
diff --git a/ui/src/app/metadata/view/MetadataEdit.js b/ui/src/app/metadata/view/MetadataEdit.js
index 6fcabd933..1e4c24d3d 100644
--- a/ui/src/app/metadata/view/MetadataEdit.js
+++ b/ui/src/app/metadata/view/MetadataEdit.js
@@ -1,11 +1,12 @@
-import React from 'react';
+import React, { useContext } from 'react';
import { MetadataForm } from '../hoc/MetadataFormContext';
import { MetadataEditor } from '../editor/MetadataEditor';
-import { useMetadataObject } from '../hoc/MetadataSelector';
+import { MetadataLoaderContext, useMetadataObject } from '../hoc/MetadataSelector';
-export function MetadataEdit({reload}) {
+export function MetadataEdit() {
const base = useMetadataObject();
+ const {reload} = useContext(MetadataLoaderContext);
return (
diff --git a/ui/src/app/metadata/view/MetadataOptions.js b/ui/src/app/metadata/view/MetadataOptions.js
index 5ae6202d8..bc1d81909 100644
--- a/ui/src/app/metadata/view/MetadataOptions.js
+++ b/ui/src/app/metadata/view/MetadataOptions.js
@@ -1,4 +1,4 @@
-import React, { useMemo } from 'react';
+import React, { useContext, useMemo } from 'react';
import { faArrowDown, faArrowUp, faHistory, faPlus, faToggleOff, faToggleOn, faTrash } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Link, useNavigate, useParams } from 'react-router-dom';
@@ -7,7 +7,7 @@ import { scroller } from 'react-scroll';
import Translate from '../../i18n/components/translate';
-import { MetadataObjectContext } from '../hoc/MetadataSelector';
+import { MetadataLoaderContext, MetadataObjectContext } from '../hoc/MetadataSelector';
import { MetadataHeader } from '../component/MetadataHeader';
import { MetadataConfiguration } from '../component/MetadataConfiguration';
import { MetadataDefinitionContext, MetadataSchemaContext } from '../hoc/MetadataSchema';
@@ -22,7 +22,7 @@ import { useMetadataSchema } from '../hooks/schema';
import { FilterableProviders } from '../domain/provider';
import { useCanEnable, useIsAdmin } from '../../core/user/UserContext';
-export function MetadataOptions ({reload}) {
+export function MetadataOptions () {
const metadata = React.useContext(MetadataObjectContext);
const definition = React.useContext(MetadataDefinitionContext);
@@ -30,6 +30,8 @@ export function MetadataOptions ({reload}) {
const processed = useMetadataSchema(definition, schema);
const navigate = useNavigate();
+ const {reload} = useContext(MetadataLoaderContext);
+
const { type, id } = useParams();
const configuration = useMetadataConfiguration([metadata], processed, definition);
diff --git a/ui/src/app/metadata/view/MetadataWizard.js b/ui/src/app/metadata/view/MetadataWizard.js
index 264a2d022..92379f720 100644
--- a/ui/src/app/metadata/view/MetadataWizard.js
+++ b/ui/src/app/metadata/view/MetadataWizard.js
@@ -6,14 +6,17 @@ import { MetadataProviderWizard } from '../wizard/MetadataProviderWizard';
import { Wizard } from '../wizard/Wizard';
import { useMetadataEntity } from '../hooks/api';
import { createNotificationAction, NotificationTypes } from '../../store/notifications/NotificationSlice';
-import { Prompt, useNavigate } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
import { useTranslator } from '../../i18n/hooks';
import { useDispatch } from 'react-redux';
+import { ShowNavContext } from '../new/NewSource';
+import { usePrompt } from '../../core/hooks/usePrompt';
-export function MetadataWizard ({type, data, onCallback, onContinue}) {
+export function MetadataWizard ({type, data, onCallback}) {
const navigate = useNavigate();
const translator = useTranslator();
+ const { setShowNav } = React.useContext(ShowNavContext);
const { post, loading, response } = useMetadataEntity(type === 'source' ? 'source' : 'provider');
@@ -48,17 +51,14 @@ export function MetadataWizard ({type, data, onCallback, onContinue}) {
}
}
+ const prompt = usePrompt(blocking, `message.unsaved-editor`);
+
return (
- {/*
- `message.unsaved-editor`
- }
- />*/}
+ {prompt}
{type === 'source' ?
-
+
:
}
diff --git a/ui/src/app/metadata/wizard/MetadataSourceProtocolSelector.js b/ui/src/app/metadata/wizard/MetadataSourceProtocolSelector.js
index cb0847d95..ae4436e51 100644
--- a/ui/src/app/metadata/wizard/MetadataSourceProtocolSelector.js
+++ b/ui/src/app/metadata/wizard/MetadataSourceProtocolSelector.js
@@ -7,11 +7,13 @@ import Form from 'react-bootstrap/Form';
import Translate from '../../i18n/components/translate';
import { InfoIcon } from '../../form/component/InfoIcon';
import { useTranslator } from '../../i18n/hooks';
-import { useMetadataSources } from '../hooks/api';
+import { useMetadataSourceProtocols, useMetadataSources } from '../hooks/api';
import { useUserGroup } from '../../core/user/UserContext';
import Button from 'react-bootstrap/Button';
-export function MetadataSourceProtocolSelector({ types = [], loading, children}) {
+export function MetadataSourceProtocolSelector({ loading, children}) {
+
+ const types = useMetadataSourceProtocols();
const [sourceNames, setSourceNames] = React.useState([]);
const [sourceIds, setSourceIds] = React.useState([]);