diff --git a/apps/mx/index.html b/apps/mx/index.html
index 0cc4e38aa7eafffd58d3c075ba5d3c3be0046a4b..01c27028866476042fb385cd6b4586d02696de6e 100644
--- a/apps/mx/index.html
+++ b/apps/mx/index.html
@@ -1,2 +1,13 @@
 <!doctype html>
-<html lang="en"></html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/favicon.ico" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Data Portal</title>
+  </head>
+  <body>
+    <div id="root"></div>
+    <script type="module" src="/src/main.tsx"></script>
+  </body>
+</html>
diff --git a/apps/mx/package.json b/apps/mx/package.json
index 375756ce91500ee9f3341a41dc17c3582fd7f2f3..9fae583ecc3b97d214520a0a4e2bdeb22b6940e3 100644
--- a/apps/mx/package.json
+++ b/apps/mx/package.json
@@ -24,6 +24,9 @@
     "@fortawesome/free-solid-svg-icons": "^6.6.0",
     "@fortawesome/react-fontawesome": "^0.2.2",
     "@tanstack/react-query": "^5.52.1",
+    "ajv": "^8.17.1",
+    "bootstrap": "^5.3.3",
+    "bootswatch": "^5.3.3",
     "lodash": "^4.17.21",
     "papaparse": "^5.4.1",
     "react": "^18.3.1",
diff --git a/apps/mx/public/config/api.config.json b/apps/mx/public/config/api.config.json
new file mode 100644
index 0000000000000000000000000000000000000000..fa5cfdd9063ff7a993a82fd05574f4fdf8f65ff4
--- /dev/null
+++ b/apps/mx/public/config/api.config.json
@@ -0,0 +1,35 @@
+{
+  "icat_url": "https://icatplus.esrf.fr/",
+  "authentication": {
+    "anonymous": {
+      "plugin": "db",
+      "username": "reader",
+      "password": "reader"
+    },
+    "autoRefresh": true,
+    "autoRefreshThresholdMinutes": "60",
+    "authenticators": [
+      {
+        "name": "OpenID",
+        "enabled": true,
+        "title": "ESRF Single Sign On",
+        "plugin": "esrf",
+        "message": "Login with ESRF SSO",
+        "openidLogoutTransferToApp": true,
+        "refreshToken": true,
+        "minValidity": 600,
+        "configuration": {
+          "authority": "https://websso.esrf.fr/auth/realms/ESRF",
+          "clientId": "icat"
+        }
+      },
+      {
+        "name": "Database",
+        "title": "Database",
+        "enabled": true,
+        "message": "",
+        "plugin": "db"
+      }
+    ]
+  }
+}
diff --git a/apps/mx/public/config/ui.config.json b/apps/mx/public/config/ui.config.json
new file mode 100644
index 0000000000000000000000000000000000000000..99dc23f62c7078e9a91824dd27a03ef9907a0995
--- /dev/null
+++ b/apps/mx/public/config/ui.config.json
@@ -0,0 +1,169 @@
+{
+  "applicationTitle": "Data Portal",
+  "facilityName": "ESRF",
+  "homePage": {
+    "policyMessage": "Public data is accessible to anyone. You need to be logged-in to visualize your data when it is under embargo. See <a href=\"https://www.esrf.fr/fr/home/UsersAndScience/UserGuide/esrf-data-policy.html\" target=\"_blank\" rel=\"noreferrer\">ESRF data policy</a> for more details."
+  },
+  "linkToPreviousVersion": {
+    "name": "Back to Data Portal V1",
+    "url": "https://data1.esrf.fr"
+  },
+  "knowledgeBasePage": "https://confluence.esrf.fr/display/DATAPOLWK/Knowledge+Base",
+  "loginForm": {
+    "accountCreationLink": "https://smis.esrf.fr/misapps/SMISWebClient/accountManager/searchExistingAccount.do?action=search",
+    "note": {
+      "enabled": true,
+      "title": "Important note",
+      "notes": [
+        {
+          "text": "In order to login to the Data Portal of the ESRF to send samples or browse embargoed data you need to be declared a member of a proposal on the ESRF User Portal. Once this is done your account will be activated <b>45 days</b> before the first experiment starts. If you need access earlier please contact the <a rel=\"noopener noreferrer\" target=\"_blank\" href=\"http://www.esrf.eu/UsersAndScience/UserGuide/Contacts\" >ESRF User Office</a>.<br/>Anonymous login to browse open data is always possible."
+        },
+        {
+          "text": "During 2019 and according to the General Data Protection Regulation, all portal users who did not consent to the <a href=\"http://www.esrf.fr/GDPR\" rel=\"noopener noreferrer\" target=\"_blank\" > User Portal Privacy Statement</a> have had their account deactivated. Please contact the <a rel=\"noopener noreferrer\" target=\"_blank\" href=\"http://www.esrf.eu/UsersAndScience/UserGuide/Contacts\" >User Office</a> if you wish to reactivate it."
+        }
+      ]
+    }
+  },
+  "userPortal": {
+    "investigationParameterPkName": "Id",
+    "link": "https://smis.esrf.fr/misapps/SMISWebClient/protected/aform/manageAForm.do?action=view&expSessionVO.pk="
+  },
+  "doi": {
+    "link": "https://doi.esrf.fr/",
+    "minimalAbstractLength": 1000,
+    "minimalTitleLength": 40,
+    "facilityPrefix": "10.15151",
+    "facilitySuffix": "ESRF-DC",
+    "referenceDoi": "https://doi.esrf.fr/10.15151/ESRF-DC-2011729981"
+  },
+  "fileBrowser": {
+    "maxFileNb": 1000
+  },
+  "imageViewer": {
+    "fileExtensions": [".png", ".jpg", ".jpeg", ".tiff", ".gif"]
+  },
+  "h5Viewer": {
+    "url": "https://hibou.esrf.fr",
+    "fileExtensions": [".hdf5", ".h5", ".nexus", ".nx", ".nxs", ".cxi"]
+  },
+  "galleryViewer": {
+    "fileExtensions": ["jpeg", "jpg", "gif", "png", "svg"]
+  },
+  "textViewer": {
+    "maxFileSize": 5000000,
+    "fileExtensions": [
+      ".txt",
+      ".log",
+      ".json",
+      ".xml",
+      ".csv",
+      ".dat",
+      ".inp",
+      ".xds",
+      ".descr",
+      ".lp",
+      ".hkl",
+      ".site",
+      ".asc",
+      ".ini"
+    ]
+  },
+  "feedback": {
+    "email": "dataportalrequests@esrf.fr",
+    "subject": "Feedback",
+    "body": "Hi,\n\n<< Please provide your feedback here. >>\n<< To report an issue, please include screenshots, reproduction steps, proposal number, beamline, etc. >>\n<< To suggest a new feature, please describe the needs this feature would fulfill. >>\n\nThanks"
+  },
+  "footer": {
+    "images": [
+      {
+        "src": "/images/esrf.jpg",
+        "alt": "ESRF",
+        "href": "https://www.esrf.fr/"
+      },
+      {
+        "src": "/images/CoreTrustSeal.png",
+        "alt": "CoreTrustSeal",
+        "href": "https://www.coretrustseal.org/"
+      }
+    ]
+  },
+  "handsonTableLicenseKey": "non-commercial-and-evaluation",
+  "globus": {
+    "enabled": true,
+    "url": "https://app.globus.org/file-manager?",
+    "collections": [
+      {
+        "root": "/data/visitor/",
+        "origin": "/data",
+        "originId": "bfc3eff4-f5ca-4b4d-8532-e9a155f3613f"
+      },
+      {
+        "root": "/data/projects/hop",
+        "origin": "/data/projects/hop",
+        "originId": "340dc883-4b0d-476c-abb0-969e1ddd9dc0"
+      },
+      {
+        "root": "/data/projects/open-datasets",
+        "origin": "/data/projects/open-datasets/",
+        "originId": "8cbe8cdc-048a-48cf-b8a0-046a6af3ba44"
+      },
+      {
+        "root": "/data/projects/paleo",
+        "origin": "/data/projects/paleo/public",
+        "originId": "e8fb6c4b-9ab0-4a1c-a79d-d51cef0b8c3d"
+      }
+    ],
+    "messageAlert": {
+      "enabled": true,
+      "message": "For users who want to download large volume of experimental data <strong>(&gt;2GB)</strong>, ESRF users can access the Globus service, please read the <a href=\"https://confluence.esrf.fr/display/SCKB/Globus\" target=\"_blank\">documentation</a> for proceeding."
+    }
+  },
+  "sample": {
+    "pageTemplateURL": "https://smis.esrf.fr/misapps/SMISWebClient/protected/samplesheet/view.do?pk=",
+    "editable": true,
+    "descriptionParameterName": "Sample_description",
+    "nonEditableParameterName": "Id",
+    "notesParameterName": "Sample_notes"
+  },
+  "logbook": {
+    "help": "https://confluence.esrf.fr/display/DATAPOLWK/Electronic+Logbook",
+    "defaultMonthPeriodForStaff": 1
+  },
+  "projects": [
+    {
+      "title": "The Human Organ Atlas",
+      "key": "The Human Organ Atlas",
+      "url": "https://human-organ-atlas.esrf.eu/datasets/{datasetId}",
+      "homepage": "https://human-organ-atlas.esrf.eu"
+    },
+    {
+      "title": "Paleontology database",
+      "key": "paleo",
+      "url": "http://paleo.esrf.fr/datasets/{datasetId}",
+      "homepage": "http://paleo.esrf.fr"
+    }
+  ],
+  "features": {
+    "reprocessing": true,
+    "logbook": true,
+    "dmp": true,
+    "logistics": true
+  },
+  "tracking": {
+    "enabled": true,
+    "url": "https://matomo-srv-1.esrf.fr/",
+    "siteId": "14",
+    "tracker": "piwik.php",
+    "script": "piwik.js"
+  },
+  "mx": {
+    "pdb_map_mtz_viewer_url": "https://moorhen.esrf.fr"
+  },
+  "logistics": {
+    "transportOrganizationEnabled": false,
+    "facilityReimbursmentEnabled": true,
+    "facilityForwarderName": "FedEX",
+    "facilityForwarderAccount": "388310561",
+    "facilityForwarderNamePickup": ["FEDEX", "DHL express", "UPS"]
+  }
+}
diff --git a/apps/mx/src/App.tsx b/apps/mx/src/App.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ef6e18e49a59b668a174c7575c96bc2213ebb785
--- /dev/null
+++ b/apps/mx/src/App.tsx
@@ -0,0 +1,50 @@
+import {
+  AuthenticatedAPIProvider,
+  AuthenticatorProvider,
+  OpenIDProvider,
+  SideNavProvider,
+  UnauthenticatedAPIProvider,
+} from '@edata-portal/core';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { AppRouter } from 'standalone/routing/AppRouter';
+import { ViewersProvider } from 'standalone/providers/ViewersProvider';
+import { ConfigProvider } from 'standalone/providers/ConfigProvider';
+import { Navigation } from 'standalone/routing/Navigation';
+import 'standalone/scss/main.scss';
+
+const queryClient = new QueryClient();
+
+function App() {
+  return (
+    <>
+      <h1>MX Microfrontend Development</h1>
+      <QueryClientProvider client={queryClient}>
+        <ConfigProvider>
+          <AppContextProviders>
+            <ViewersProvider>
+              <SideNavProvider>
+                <AppRouter>
+                  <Navigation />
+                </AppRouter>
+              </SideNavProvider>
+            </ViewersProvider>
+          </AppContextProviders>
+        </ConfigProvider>
+      </QueryClientProvider>
+    </>
+  );
+}
+
+function AppContextProviders({ children }: { children: React.ReactNode }) {
+  return (
+    <UnauthenticatedAPIProvider>
+      <AuthenticatorProvider>
+        <OpenIDProvider>
+          <AuthenticatedAPIProvider>{children}</AuthenticatedAPIProvider>
+        </OpenIDProvider>
+      </AuthenticatorProvider>
+    </UnauthenticatedAPIProvider>
+  );
+}
+
+export default App;
diff --git a/apps/mx/src/main.tsx b/apps/mx/src/main.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..dcc8a0d47118c65d1c4a1eb2db2fb71c4f2828f4
--- /dev/null
+++ b/apps/mx/src/main.tsx
@@ -0,0 +1,9 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from 'App';
+
+ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
+  <React.StrictMode>
+    <App />
+  </React.StrictMode>,
+);
diff --git a/apps/mx/src/standalone/InvestigationDatasets.tsx b/apps/mx/src/standalone/InvestigationDatasets.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c41646e3969fb462173492d349300bbb955d4264
--- /dev/null
+++ b/apps/mx/src/standalone/InvestigationDatasets.tsx
@@ -0,0 +1,32 @@
+import { usePath, useViewers } from '@edata-portal/core';
+import {
+  useGetEndpoint,
+  INVESTIGATION_LIST_ENDPOINT,
+} from '@edata-portal/icat-plus-api';
+import { Alert } from 'react-bootstrap';
+
+/**
+ * Fetches and displays the datasets related to a specific investigation.
+ *
+ * This component:
+ * - Retrieves the `investigationId` from the URL path.
+ * - Fetches the corresponding investigation using `useGetEndpoint`.
+ * - Renders an alert if no investigation is found.
+ * - Uses `viewInvestigation` from `useViewers()` to render the investigation details.
+ *
+ * @returns {JSX.Element} Investigation details or an error alert.
+ */
+export default function InvestigationDatasets(): JSX.Element {
+  const investigationId = usePath('investigationId');
+  const investigations = useGetEndpoint({
+    endpoint: INVESTIGATION_LIST_ENDPOINT,
+    params: { ids: investigationId },
+  });
+  const { viewInvestigation } = useViewers();
+
+  if (!investigations || investigations.length === 0) {
+    return <Alert variant="danger">Could not find investigation</Alert>;
+  }
+
+  return viewInvestigation(investigations[0]);
+}
diff --git a/apps/mx/src/standalone/providers/ConfigProvider.tsx b/apps/mx/src/standalone/providers/ConfigProvider.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8d1f07f5bdb487bbbadb1487e2800201b61e40ce
--- /dev/null
+++ b/apps/mx/src/standalone/providers/ConfigProvider.tsx
@@ -0,0 +1,53 @@
+import { ConfigContext } from '@edata-portal/core';
+import { useSuspenseQueries } from '@tanstack/react-query';
+import { useMemo } from 'react';
+declare global {
+  interface Window {
+    remoteUrls: any;
+  }
+}
+
+export function ConfigProvider({ children }: { children: React.ReactNode }) {
+  const [api, ui, techniques] = useSuspenseQueries({
+    queries: [
+      {
+        queryKey: ['api.config'],
+        queryFn: async () => {
+          const response = await fetch('../config/api.config.json');
+          return await response.json();
+        },
+      },
+      {
+        queryKey: ['ui.config'],
+        queryFn: async () => {
+          const response = await fetch('../config/ui.config.json');
+          return await response.json();
+        },
+      },
+      {
+        queryKey: ['techniques.config'],
+        queryFn: async () => {
+          return [
+            {
+              name: 'Crystallography',
+              shortname: 'MX',
+            },
+          ];
+        },
+      },
+    ],
+  });
+
+  const value = useMemo(
+    () => ({
+      api: api.data,
+      ui: ui.data,
+      techniques: techniques.data,
+    }),
+    [api.data, techniques.data, ui.data],
+  );
+
+  return (
+    <ConfigContext.Provider value={value}>{children}</ConfigContext.Provider>
+  );
+}
diff --git a/apps/mx/src/standalone/providers/ViewersProvider.tsx b/apps/mx/src/standalone/providers/ViewersProvider.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..52b191cb83382968accd2944f2bb431f532dca83
--- /dev/null
+++ b/apps/mx/src/standalone/providers/ViewersProvider.tsx
@@ -0,0 +1,33 @@
+import { ViewersContext } from '@edata-portal/core';
+import { DatasetViewer } from 'standalone/viewers/DatasetViewer';
+import { InvestigationViewer } from 'standalone/viewers/InvestigationViewer';
+import { SampleViewer } from 'standalone/viewers/SampleViewer';
+
+export function ViewersProvider({ children }: { children: React.ReactNode }) {
+  return (
+    <ViewersContext.Provider
+      value={{
+        viewSample: (sample, props) => (
+          <SampleViewer key={sample.id} sample={sample} props={props} />
+        ),
+        viewDataset: (dataset, type, props) => (
+          <DatasetViewer
+            key={dataset.id}
+            dataset={dataset}
+            type={type}
+            props={props}
+          />
+        ),
+        viewInvestigation: (investigation, props) => (
+          <InvestigationViewer
+            key={investigation.id}
+            investigation={investigation}
+            props={props}
+          />
+        ),
+      }}
+    >
+      {children}
+    </ViewersContext.Provider>
+  );
+}
diff --git a/apps/mx/src/standalone/routing/AppRouter.tsx b/apps/mx/src/standalone/routing/AppRouter.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..1eae24f459d3e8a416fd50fd14fbf4a0995656a0
--- /dev/null
+++ b/apps/mx/src/standalone/routing/AppRouter.tsx
@@ -0,0 +1,33 @@
+import InvestigationDatasets from 'standalone/InvestigationDatasets';
+import { useMemo } from 'react';
+import {
+  createBrowserRouter,
+  RouteObject,
+  RouterProvider,
+} from 'react-router-dom';
+
+export const routes: RouteObject[] = [
+  {
+    path: '/investigation/:investigationId',
+    element: <InvestigationDatasets></InvestigationDatasets>,
+  },
+  {
+    path: '*',
+    element: <div>Cheers</div>,
+  },
+];
+
+export function AppRouter({ children }: { children: JSX.Element }) {
+  const router = useMemo(() => {
+    return createBrowserRouter([
+      {
+        element: <>{children}</>,
+        children: routes,
+        id: 'home',
+        handle: { breadcrumb: 'Home' },
+      },
+    ]);
+  }, [children]);
+
+  return <RouterProvider router={router} />;
+}
diff --git a/apps/mx/src/standalone/routing/Navigation.tsx b/apps/mx/src/standalone/routing/Navigation.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..df81786783e3d283c6607b8e6879b690fe7e92ab
--- /dev/null
+++ b/apps/mx/src/standalone/routing/Navigation.tsx
@@ -0,0 +1,38 @@
+import { Container } from 'react-bootstrap';
+import { Outlet } from 'react-router-dom';
+import { SideNavRenderer } from '@edata-portal/core';
+
+export function Navigation() {
+  return (
+    <div
+      style={{
+        position: 'fixed',
+        top: 0,
+        left: 0,
+        right: 0,
+        bottom: 0,
+        display: 'flex',
+        flexDirection: 'column',
+        overflow: 'hidden',
+      }}
+    >
+      <Container
+        style={{
+          paddingTop: 20,
+          overflow: 'auto',
+          height: '100%',
+          position: 'relative',
+          display: 'flex',
+          flexDirection: 'column',
+          justifyContent: 'space-between',
+        }}
+        fluid
+        className="main"
+      >
+        <SideNavRenderer>
+          <Outlet />
+        </SideNavRenderer>
+      </Container>
+    </div>
+  );
+}
diff --git a/apps/mx/src/standalone/scss/colors.scss b/apps/mx/src/standalone/scss/colors.scss
new file mode 100644
index 0000000000000000000000000000000000000000..210d6faf4d6d5865d5c55e09b542a71294ab1b0a
--- /dev/null
+++ b/apps/mx/src/standalone/scss/colors.scss
@@ -0,0 +1,29 @@
+// custom theme colors
+
+$primary: #2c3e50;
+$secondary: #456c74;
+$success: #18bc9c;
+$info: #108cdf;
+$warning: #f39c12;
+$danger: #e74c3c;
+$light: #ecf0f1;
+$dark: #222222;
+
+$dataset-raw: #b4ccd1;
+$dataset-processed: #f9e69e;
+$sample: #4d3d5f;
+
+:root {
+  --primary: #{$primary};
+  --secondary: #{$secondary};
+  --success: #{$success};
+  --info: #{$info};
+  --warning: #{$warning};
+  --danger: #{$danger};
+  --light: #{$light};
+  --dark: #{$dark};
+
+  --dataset-raw: #{$dataset-raw};
+  --dataset-processed: #{$dataset-processed};
+  --sample: #{$sample};
+}
diff --git a/apps/mx/src/standalone/scss/images/esrf-white.png b/apps/mx/src/standalone/scss/images/esrf-white.png
new file mode 100644
index 0000000000000000000000000000000000000000..a3c925198bc7246316308878f9ffda7079adc867
Binary files /dev/null and b/apps/mx/src/standalone/scss/images/esrf-white.png differ
diff --git a/apps/mx/src/standalone/scss/main.scss b/apps/mx/src/standalone/scss/main.scss
new file mode 100644
index 0000000000000000000000000000000000000000..7c94a23909253e29305d0ecf0f920abde601c1bf
--- /dev/null
+++ b/apps/mx/src/standalone/scss/main.scss
@@ -0,0 +1,60 @@
+@import 'bootswatch/dist/flatly/variables';
+@import './colors.scss';
+@import 'bootstrap/scss/bootstrap';
+
+$web-font-path: false; //disable loading of web fonts
+@import 'bootswatch/dist/flatly/bootswatch';
+
+body {
+  font-size: 0.9em;
+}
+
+.navbar-brand {
+  background: url('images/esrf-white.png') no-repeat;
+  background-size: contain;
+  padding-left: 70px;
+}
+
+.breadcrumb {
+  margin: 0px;
+  padding: 0px;
+}
+
+.table {
+  --bs-table-bg: 'none';
+}
+
+.monospace {
+  font-family: monospace;
+}
+
+.bg-dataset-raw {
+  background-color: $dataset-raw;
+  color: color-contrast($dataset-raw);
+  a {
+    color: color-contrast($dataset-raw);
+  }
+}
+
+.bg-dataset-processed {
+  background-color: $dataset-processed;
+  color: color-contrast($dataset-processed);
+  a {
+    color: color-contrast($dataset-processed);
+  }
+}
+
+.bg-sample {
+  background-color: $sample;
+  color: color-contrast($sample);
+  a {
+    color: color-contrast($sample);
+  }
+}
+
+.bg-secondary {
+  color: color-contrast($secondary);
+  a {
+    color: color-contrast($secondary);
+  }
+}
diff --git a/apps/mx/src/standalone/viewers/DatasetViewer.tsx b/apps/mx/src/standalone/viewers/DatasetViewer.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6b1437f5772b46ee5af3df60ef12d553c234a20c
--- /dev/null
+++ b/apps/mx/src/standalone/viewers/DatasetViewer.tsx
@@ -0,0 +1,33 @@
+import { DatasetViewerType } from '@edata-portal/core';
+import type { Dataset } from '@edata-portal/icat-plus-api';
+import { Alert } from 'react-bootstrap';
+import MXDatasetDetailsViewer from 'viewers/MXDatasetDetailsViewer';
+import MXDatasetSnapshotViewer from 'viewers/MXDatasetSnapshotViewer';
+
+/**
+ * Renders a dataset viewer based on the specified type.
+ *
+ * @param {Object} props - Component props.
+ * @param {Dataset} props.dataset - The dataset object to display.
+ * @param {any} [props.props] - Additional props to be passed to the viewer component.
+ * @param {DatasetViewerType} props.type - The type of viewer to display.
+ *
+ * @returns {JSX.Element} The appropriate dataset viewer component or an error alert.
+ */
+export function DatasetViewer({
+  dataset,
+  props,
+  type,
+}: {
+  dataset: Dataset;
+  props?: any;
+  type: DatasetViewerType;
+}) {
+  if (type === 'details') {
+    return <MXDatasetDetailsViewer dataset={dataset} {...props} />;
+  }
+  if (type === 'snapshot') {
+    return <MXDatasetSnapshotViewer {...props} dataset={dataset} />;
+  }
+  return <Alert variant="danger">No viewer found</Alert>;
+}
diff --git a/apps/mx/src/standalone/viewers/InvestigationViewer.tsx b/apps/mx/src/standalone/viewers/InvestigationViewer.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ee860d3757128cce51a31ad2547ee0536e5fc66d
--- /dev/null
+++ b/apps/mx/src/standalone/viewers/InvestigationViewer.tsx
@@ -0,0 +1,18 @@
+import { Investigation } from '@edata-portal/icat-plus-api';
+import MXInvestigationViewer from 'viewers/MXInvestigationViewer';
+
+export function InvestigationViewer({
+  investigation,
+  props,
+}: {
+  investigation: Investigation;
+  props?: any;
+}) {
+  return (
+    <MXInvestigationViewer
+      investigation={investigation}
+      nested={true}
+      {...props}
+    />
+  );
+}
diff --git a/apps/mx/src/standalone/viewers/SampleViewer.tsx b/apps/mx/src/standalone/viewers/SampleViewer.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..adb11bb9a7338d43ca0bab50cabd53cf3fd919a5
--- /dev/null
+++ b/apps/mx/src/standalone/viewers/SampleViewer.tsx
@@ -0,0 +1,12 @@
+import type { Sample } from '@edata-portal/icat-plus-api';
+import MXSampleViewer from 'viewers/MXSampleViewer';
+
+export function SampleViewer({
+  sample,
+  props,
+}: {
+  sample: Sample;
+  props?: any;
+}) {
+  return <MXSampleViewer sample={sample} {...props} />;
+}
diff --git a/apps/mx/tsconfig.json b/apps/mx/tsconfig.json
index 54f4b84b0d1a9f04e7e7dc24a0f78ad19c34a379..bc6526fe14ebb2d7a79443e1c541210de3dd9f5b 100644
--- a/apps/mx/tsconfig.json
+++ b/apps/mx/tsconfig.json
@@ -1,6 +1,6 @@
 {
   "extends": "../../tsconfig.json",
-  "include": ["src"],
+  "include": ["src", "public/config"],
   "compilerOptions": {
     "baseUrl": "src",
     "outDir": "dist",
diff --git a/apps/portal/package.json b/apps/portal/package.json
index 99e025f9e0dca3611e5146cd7c0376860a61e4ab..4989c91080965ad649ea1f15b81fcf7634e6d3e9 100644
--- a/apps/portal/package.json
+++ b/apps/portal/package.json
@@ -47,7 +47,6 @@
     "react-datepicker": "^7.3.0",
     "react-dom": "^18.3.1",
     "react-intersection-observer": "^9.13.0",
-    "react-oidc-context": "^3.1.0",
     "react-router-dom": "^6.26.1",
     "react-select": "^5.8.0"
   },
diff --git a/apps/portal/src/App.tsx b/apps/portal/src/App.tsx
index 8e92250894a6720da1b2587167bafa455385577c..9f49560e4cea040fcee30e741aeaae93d082ae37 100644
--- a/apps/portal/src/App.tsx
+++ b/apps/portal/src/App.tsx
@@ -1,21 +1,21 @@
-import { Loading } from '@edata-portal/core';
+import {
+  AuthenticatedAPIProvider,
+  AuthenticatorProvider,
+  Loading,
+  OpenIDProvider,
+  SideNavProvider,
+  UnauthenticatedAPIProvider,
+} from '@edata-portal/core';
 import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import { AuthenticatorProvider } from 'authentication/authenticator';
-import { OpenIDProvider } from 'authentication/openid';
 import { AppRouter } from 'components/routing/AppRouter';
 import { Suspense } from 'react';
 import { Navigation } from 'components/navigation/Navigation';
 import { ViewersProvider } from 'components/viewers/ViewersProvider';
-import { SideNavProvider } from 'components/navigation/SideNav';
 import {
   NotifyProvider,
   NotifyRenderer,
 } from 'components/notify/NotifyProvider';
 import { ReprocessContextProvider } from 'components/reprocessing/ReprocessContext';
-import {
-  AuthenticatedAPIProvider,
-  UnauthenticatedAPIProvider,
-} from 'components/api/APIProvider';
 import { TrackRoutes } from 'components/routing/TrackRoutes';
 import { ConfigProvider } from 'ConfigProvider';
 import { RemoteProvider } from 'remotes';
diff --git a/apps/portal/src/components/navigation/Navigation.tsx b/apps/portal/src/components/navigation/Navigation.tsx
index de286936adb2c7fa0632a70ce557a0716d0822fa..c579aab7e9261af0832726d1a5021338d3dc5660 100644
--- a/apps/portal/src/components/navigation/Navigation.tsx
+++ b/apps/portal/src/components/navigation/Navigation.tsx
@@ -3,11 +3,10 @@ import { Footer } from 'components/navigation/Footer';
 import { Breadcrumbs } from 'components/navigation/Breadcrumbs';
 import { Outlet } from 'react-router-dom';
 import { ShowLoginPage } from 'components/usermanagement/Login';
-import { SideNavRenderer } from 'components/navigation/SideNav';
 import { Header } from 'components/navigation/header/Header';
 import InformationMessage from 'components/messages/InformationMessages';
 import { Suspense } from 'react';
-import { Loading } from '@edata-portal/core';
+import { Loading, SideNavRenderer } from '@edata-portal/core';
 
 export function Navigation() {
   return (
diff --git a/apps/portal/src/components/navigation/header/Header.tsx b/apps/portal/src/components/navigation/header/Header.tsx
index 750c31668bd55822a891c2449740dcc06e20a487..fe5b915f39eff661743cb59d005d7444d8ff38c8 100644
--- a/apps/portal/src/components/navigation/header/Header.tsx
+++ b/apps/portal/src/components/navigation/header/Header.tsx
@@ -1,7 +1,6 @@
 import { Nav, Navbar } from 'react-bootstrap';
 import { NavLink } from 'react-router-dom';
 import { useState } from 'react';
-import { useAuthenticator } from 'authentication/authenticator';
 import { HeaderItem } from 'components/navigation/header/HeaderItem';
 import { HeaderDropdownItem } from 'components/navigation/header/HeaderDropdownItem';
 import { HeaderSearchMenu } from 'components/navigation/header/HeaderSearchMenu';
@@ -10,7 +9,7 @@ import { HeaderSelectionMenu } from 'components/navigation/header/HeaderSelectio
 import { HeaderAdminMenu } from 'components/navigation/header/HeaderAdminMenu';
 import { HeaderHelpButton } from 'components/navigation/header/HeaderHelpButton';
 import { HeaderReprocessMenu } from 'components/navigation/header/HeaderReprocessMenu';
-import { useConfig } from '@edata-portal/core';
+import { useAuthenticator, useConfig } from '@edata-portal/core';
 export function Header() {
   const authenticator = useAuthenticator();
 
diff --git a/apps/portal/src/components/navigation/header/HeaderUserMenu.tsx b/apps/portal/src/components/navigation/header/HeaderUserMenu.tsx
index 0d20a5429bf519b4d5ba91dc3cefdd74f206a11b..99e3bca0bd43a207e8d7922739ab5a6828c007cc 100644
--- a/apps/portal/src/components/navigation/header/HeaderUserMenu.tsx
+++ b/apps/portal/src/components/navigation/header/HeaderUserMenu.tsx
@@ -1,7 +1,10 @@
-import { Button, useLocalStorageValue } from '@edata-portal/core';
+import {
+  Button,
+  useAuthenticator,
+  useLocalStorageValue,
+} from '@edata-portal/core';
 import { faUser } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { useAuthenticator } from 'authentication/authenticator';
 import { AffiliationSwitch } from 'components/usermanagement/AffiliationSwitch';
 import { Logout } from 'components/usermanagement/Logout';
 import { useEffect, useState } from 'react';
diff --git a/apps/portal/src/components/routing/AppRouter.tsx b/apps/portal/src/components/routing/AppRouter.tsx
index eae4a2805ab1ce2212ab466e927af3b7aa940311..2cee80e6aaf91730d420bd19cf6905cd1b9747f7 100644
--- a/apps/portal/src/components/routing/AppRouter.tsx
+++ b/apps/portal/src/components/routing/AppRouter.tsx
@@ -14,6 +14,5 @@ export function AppRouter({ children }: { children: JSX.Element }) {
       },
     ]);
   }, [children]);
-
   return <RouterProvider router={router} />;
 }
diff --git a/apps/portal/src/components/usermanagement/AffiliationSwitch.tsx b/apps/portal/src/components/usermanagement/AffiliationSwitch.tsx
index cb68df33d8a56992ba841f338d329ff56446d505..e7d5dd6eb1395b1b8a6b991518756444f890978f 100644
--- a/apps/portal/src/components/usermanagement/AffiliationSwitch.tsx
+++ b/apps/portal/src/components/usermanagement/AffiliationSwitch.tsx
@@ -1,5 +1,4 @@
-import { useUser } from '@edata-portal/core';
-import { useAppOpenID } from 'authentication/openid';
+import { useAppOpenID, useUser } from '@edata-portal/core';
 import { UserAffiliationLabel } from 'components/usermanagement/UserAffiliationLabel';
 import { Dropdown } from 'react-bootstrap';
 
diff --git a/apps/portal/src/components/usermanagement/Login.tsx b/apps/portal/src/components/usermanagement/Login.tsx
index 453f75a7b0c11259e15cef35df6f0609fa8f8c0f..716492396108418baa67b0f19e38b8d64f8478dd 100644
--- a/apps/portal/src/components/usermanagement/Login.tsx
+++ b/apps/portal/src/components/usermanagement/Login.tsx
@@ -1,9 +1,9 @@
 import { faEnvelope } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { useAuthenticator } from 'authentication/authenticator';
 import {
   type GenericAuthenticator,
   OpenIDAuthenticator,
+  useAuthenticator,
   useConfig,
 } from '@edata-portal/core';
 import { Alert, Modal, Stack } from 'react-bootstrap';
diff --git a/apps/portal/src/components/usermanagement/LoginForm.tsx b/apps/portal/src/components/usermanagement/LoginForm.tsx
index 2b7e12f8211ddebec03be0168e3d002fce9b3d1f..9db6be856748f9da2b083acb77214d7b4492a9e8 100644
--- a/apps/portal/src/components/usermanagement/LoginForm.tsx
+++ b/apps/portal/src/components/usermanagement/LoginForm.tsx
@@ -1,5 +1,7 @@
-import { useAuthenticator } from 'authentication/authenticator';
-import type { GenericAuthenticator } from '@edata-portal/core';
+import {
+  useAuthenticator,
+  type GenericAuthenticator,
+} from '@edata-portal/core';
 import type { FormEvent } from 'react';
 import { useCallback, useEffect, useRef, useState } from 'react';
 import {
diff --git a/apps/portal/src/components/usermanagement/LoginOpenID.tsx b/apps/portal/src/components/usermanagement/LoginOpenID.tsx
index 6bdb5a9918167465fbc83efab934feee451f14b1..aba0dfec4a83c4b22a96b5e5d11d2b8beb4dc529 100644
--- a/apps/portal/src/components/usermanagement/LoginOpenID.tsx
+++ b/apps/portal/src/components/usermanagement/LoginOpenID.tsx
@@ -1,7 +1,6 @@
-import { OpenIDAuthenticator } from '@edata-portal/core';
+import { OpenIDAuthenticator, useAppOpenID } from '@edata-portal/core';
 import { faLock, faWarning } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { useAppOpenID } from 'authentication/openid';
 import { Alert, Button } from 'react-bootstrap';
 
 export function LoginOpenID({
diff --git a/apps/portal/src/components/usermanagement/Logout.tsx b/apps/portal/src/components/usermanagement/Logout.tsx
index a7a9c1d8bd5451e1836cfd1a443b9609ce569452..959337c94de34b6aea3e7ec352a4936574506e03 100644
--- a/apps/portal/src/components/usermanagement/Logout.tsx
+++ b/apps/portal/src/components/usermanagement/Logout.tsx
@@ -1,5 +1,4 @@
-import { useAuthenticator } from 'authentication/authenticator';
-import { useAppOpenID } from 'authentication/openid';
+import { useAppOpenID, useAuthenticator } from '@edata-portal/core';
 import { NavDropdown } from 'react-bootstrap';
 
 export function Logout({ onClick }: { onClick: () => void }) {
diff --git a/packages/core/package.json b/packages/core/package.json
index 64e7c619829a166469eb354a8199db8633b42e74..3a927685a42223310417f0472c7aab587c5f1894 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -38,6 +38,7 @@
     "react-final-form": "^6.5.9",
     "react-final-form-arrays": "^3.1.4",
     "react-intersection-observer": "^9.13.0",
+    "react-oidc-context": "^3.2.0",
     "react-plotly.js": "^2.6.0",
     "react-resizable-panels": "^2.1.1",
     "react-rnd": "^10.4.12",
diff --git a/apps/portal/src/components/LoadingIndicator.tsx b/packages/core/src/components/LoadingIndicator.tsx
similarity index 100%
rename from apps/portal/src/components/LoadingIndicator.tsx
rename to packages/core/src/components/LoadingIndicator.tsx
diff --git a/apps/portal/src/authentication/anonymous.ts b/packages/core/src/components/authentication/anonymous.ts
similarity index 93%
rename from apps/portal/src/authentication/anonymous.ts
rename to packages/core/src/components/authentication/anonymous.ts
index cc39e10592bdac005d54004175c8d0b24ceac730..965bae2755ad7f7ae3bec12796cb2291e836922c 100644
--- a/apps/portal/src/authentication/anonymous.ts
+++ b/packages/core/src/components/authentication/anonymous.ts
@@ -1,11 +1,11 @@
-import { useConfig } from '@edata-portal/core';
 import {
   SESSION_CREATE_ENDPOINT,
   useMutateEndpoint,
   IcatUser,
 } from '@edata-portal/icat-plus-api';
 import { CancelledError } from '@tanstack/react-query';
-import { usePersistedUserState } from 'authentication/persist';
+import { usePersistedUserState } from 'components/authentication/persist';
+import { useConfig } from 'context';
 import { useCallback, useEffect } from 'react';
 
 const PERSISTED_ANONYMOUS_KEY = 'anonymous-user';
diff --git a/apps/portal/src/authentication/authenticator.tsx b/packages/core/src/components/authentication/authenticator.tsx
similarity index 92%
rename from apps/portal/src/authentication/authenticator.tsx
rename to packages/core/src/components/authentication/authenticator.tsx
index b3c679320f359715712d8258eb1cc88bbe282739..95c4c6b82afe2a84b1f8c109425e37f50ea5be5c 100644
--- a/apps/portal/src/authentication/authenticator.tsx
+++ b/packages/core/src/components/authentication/authenticator.tsx
@@ -1,13 +1,14 @@
-import { GenericAuthenticator, Loading, UserContext } from '@edata-portal/core';
 import React, { useCallback, useMemo, useState } from 'react';
 import {
   SESSION_CREATE_ENDPOINT,
   IcatUser,
   useMutateEndpoint,
 } from '@edata-portal/icat-plus-api';
-import { useAnonymous } from 'authentication/anonymous';
-import { usePersistedUserState } from 'authentication/persist';
-import { useUserState } from 'authentication/userState';
+import { GenericAuthenticator, UserContext } from 'context';
+import { usePersistedUserState } from 'components/authentication/persist';
+import { useUserState } from 'components/authentication/userState';
+import { useAnonymous } from 'components/authentication/anonymous';
+import { Loading } from 'components/utils';
 
 export interface AuthenticationState {
   login: (
diff --git a/packages/core/src/components/authentication/index.ts b/packages/core/src/components/authentication/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fb3eb566619feb20ef066e73022bd45d5be090be
--- /dev/null
+++ b/packages/core/src/components/authentication/index.ts
@@ -0,0 +1,2 @@
+export * from './authenticator';
+export * from './openid';
diff --git a/apps/portal/src/authentication/openid.tsx b/packages/core/src/components/authentication/openid.tsx
similarity index 93%
rename from apps/portal/src/authentication/openid.tsx
rename to packages/core/src/components/authentication/openid.tsx
index 803c65603869e273632653df90d8de90aa17cfdf..9602825baa7c55ce43c0a3dd65d6730f6f290c87 100644
--- a/apps/portal/src/authentication/openid.tsx
+++ b/packages/core/src/components/authentication/openid.tsx
@@ -1,4 +1,3 @@
-import { OpenIDAuthenticator, useConfig } from '@edata-portal/core';
 import React, {
   createContext,
   useCallback,
@@ -7,10 +6,10 @@ import React, {
   useMemo,
   useState,
 } from 'react';
-import { useAuthenticator } from 'authentication/authenticator';
 import { AuthProvider, useAuth, hasAuthParams } from 'react-oidc-context';
 import { Spinner } from 'react-bootstrap';
-import { useNavigate } from 'react-router-dom';
+import { OpenIDAuthenticator, useConfig } from 'context';
+import { useAuthenticator } from 'components/authentication/authenticator';
 
 export function useOpenIDConfig() {
   const config = useConfig();
@@ -235,17 +234,18 @@ export function useAppOpenID() {
 const PARAMS = ['state', 'code', 'session_state', 'error'] as const;
 
 function useClearParams() {
-  // We need to manually clear the OpenID query params after login, the openID lib does not do it.
-  const navigate = useNavigate();
-
   return useCallback(() => {
     if (!hasAuthParams()) return;
+
     const params = new URLSearchParams(window.location.search);
     if (!PARAMS.some((p) => params.has(p))) return;
+
     PARAMS.forEach((p) => params.delete(p));
-    navigate({
-      pathname: window.location.pathname,
-      search: params.toString(),
-    });
-  }, [navigate]);
+
+    window.history.replaceState(
+      null,
+      '',
+      `${window.location.pathname}?${params.toString()}`,
+    );
+  }, []);
 }
diff --git a/apps/portal/src/authentication/persist.ts b/packages/core/src/components/authentication/persist.ts
similarity index 94%
rename from apps/portal/src/authentication/persist.ts
rename to packages/core/src/components/authentication/persist.ts
index 3fc7a40e007e81531d8c13228baed7108f9e5319..fd8e1a9b77fb22e3070abc1f9316cf54cb596e05 100644
--- a/apps/portal/src/authentication/persist.ts
+++ b/packages/core/src/components/authentication/persist.ts
@@ -1,5 +1,5 @@
 import type { IcatUser } from '@edata-portal/icat-plus-api';
-import { useUserRefresher } from 'authentication/refresh';
+import { useUserRefresher } from 'components/authentication/refresh';
 import { useCallback, useMemo, useState } from 'react';
 
 /**
diff --git a/apps/portal/src/authentication/refresh.ts b/packages/core/src/components/authentication/refresh.ts
similarity index 97%
rename from apps/portal/src/authentication/refresh.ts
rename to packages/core/src/components/authentication/refresh.ts
index fd046013725eaef441e1ce1eaa2caf839a104061..f55fe88f8c87d194b2073e5c70610cc485be2e9d 100644
--- a/apps/portal/src/authentication/refresh.ts
+++ b/packages/core/src/components/authentication/refresh.ts
@@ -1,4 +1,3 @@
-import { addToDate, parseDate, useConfig } from '@edata-portal/core';
 import {
   SESSION_BY_ID_ENDPOINT,
   SESSION_REFRESH_ENDPOINT,
@@ -6,6 +5,8 @@ import {
   useMutateEndpoint,
   useAsyncFetchEndpoint,
 } from '@edata-portal/icat-plus-api';
+import { useConfig } from 'context';
+import { addToDate, parseDate } from 'helpers';
 import { useCallback, useEffect } from 'react';
 
 export function useUserRefresher(
diff --git a/apps/portal/src/authentication/userState.tsx b/packages/core/src/components/authentication/userState.tsx
similarity index 80%
rename from apps/portal/src/authentication/userState.tsx
rename to packages/core/src/components/authentication/userState.tsx
index 1bb2a38374c7e5b193fabf21f8cc7ec6c8ea610b..9a2ce9021912c1b7399f844fcecb01049d004f2b 100644
--- a/apps/portal/src/authentication/userState.tsx
+++ b/packages/core/src/components/authentication/userState.tsx
@@ -1,5 +1,5 @@
 import type { IcatUser } from '@edata-portal/icat-plus-api';
-import { useUserRefresher } from 'authentication/refresh';
+import { useUserRefresher } from 'components/authentication/refresh';
 import { useState } from 'react';
 
 export function useUserState() {
diff --git a/packages/core/src/components/dataset/generic/DatasetList.tsx b/packages/core/src/components/dataset/generic/DatasetList.tsx
index 63184b98d2f119fdf8f7a617af07406d96710778..1ed3c96ac1b714b627d257eff4f4ad3ac4df1ef8 100644
--- a/packages/core/src/components/dataset/generic/DatasetList.tsx
+++ b/packages/core/src/components/dataset/generic/DatasetList.tsx
@@ -50,7 +50,6 @@ export function DatasetList(
 ) {
   // State to handle scrolling to a specific sample
   const [scrollToSample, setScrollToSample] = useParam('scrollToSample', '');
-
   // Callback to reset scroll state after scrolling is done
   const onScrollDone = useCallback(() => {
     setScrollToSample(undefined);
diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts
index 47b7fd03f11a6bc89fc01bf8eed6340ac40b1382..489d744c0ca3e788a8c1c786f2b313d6dad03223 100644
--- a/packages/core/src/components/index.ts
+++ b/packages/core/src/components/index.ts
@@ -15,3 +15,5 @@ export * from './browse';
 export * from './filters';
 export * from './gridgraph';
 export * from './tabs';
+export * from './authentication';
+export * from './navigation';
diff --git a/apps/portal/src/components/navigation/SideNav.tsx b/packages/core/src/components/navigation/SideNav.tsx
similarity index 96%
rename from apps/portal/src/components/navigation/SideNav.tsx
rename to packages/core/src/components/navigation/SideNav.tsx
index 39a738fa0506e43763851326e822f825046d587a..5ee93a01fa7752680263e189335a706a832da9e2 100644
--- a/apps/portal/src/components/navigation/SideNav.tsx
+++ b/packages/core/src/components/navigation/SideNav.tsx
@@ -1,13 +1,10 @@
-import {
-  SideNavContext,
-  SideNavNode,
-  UpdateSideNavContext,
-  immutableArray,
-  useBreakpointValue,
-} from '@edata-portal/core';
 import { faAnglesLeft, faAnglesRight } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { LoadingIndicator } from 'components/LoadingIndicator';
+import { SideNavContext, SideNavNode, UpdateSideNavContext } from 'context';
+import { immutableArray } from 'helpers';
+import { useBreakpointValue } from 'hooks';
+
 import React from 'react';
 import { Button, Container } from 'react-bootstrap';
 
diff --git a/packages/core/src/components/navigation/index.ts b/packages/core/src/components/navigation/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..caaad63a9150c18434f08cb70fe29b94e4dbc8b4
--- /dev/null
+++ b/packages/core/src/components/navigation/index.ts
@@ -0,0 +1 @@
+export * from './SideNav';
diff --git a/packages/core/src/index.tsx b/packages/core/src/index.tsx
index 8eccab12f5cf3e6cff10707e80ab5a0884487fce..94cfafce2e5b6ef406ee39db3a7816e93fe7eac4 100644
--- a/packages/core/src/index.tsx
+++ b/packages/core/src/index.tsx
@@ -5,3 +5,5 @@ export * from './hooks';
 export * from './helpers';
 
 export * from './context';
+
+export * from './provider';
diff --git a/apps/portal/src/components/api/APIProvider.tsx b/packages/core/src/provider/APIProvider.tsx
similarity index 95%
rename from apps/portal/src/components/api/APIProvider.tsx
rename to packages/core/src/provider/APIProvider.tsx
index 570372f7a10c02e9250c27fdd5b5b0ed43a8dd1c..3cbc87ee8e85ea46c741660c1428b27bc04bfe0f 100644
--- a/apps/portal/src/components/api/APIProvider.tsx
+++ b/packages/core/src/provider/APIProvider.tsx
@@ -1,5 +1,5 @@
-import { useConfig, useNotify, useUser } from '@edata-portal/core';
 import { IcatPlusAPIContext } from '@edata-portal/icat-plus-api';
+import { useConfig, useNotify, useUser } from 'context';
 import { useMemo, type PropsWithChildren } from 'react';
 
 export function UnauthenticatedAPIProvider({
diff --git a/packages/core/src/provider/index.tsx b/packages/core/src/provider/index.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..09dc067729a86e00750df338b1b63f021e88982f
--- /dev/null
+++ b/packages/core/src/provider/index.tsx
@@ -0,0 +1 @@
+export * from './APIProvider';
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f5b31d25a30656a7f071cd99fdb2114954308cb6..037e18938371fec673c5acf08f625d01eb7c7b7e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -523,6 +523,15 @@ importers:
       '@tanstack/react-query':
         specifier: ^5.52.1
         version: 5.52.1(react@18.3.1)
+      ajv:
+        specifier: ^8.17.1
+        version: 8.17.1
+      bootstrap:
+        specifier: ^5.3.3
+        version: 5.3.3(@popperjs/core@2.11.8)
+      bootswatch:
+        specifier: ^5.3.3
+        version: 5.3.3
       lodash:
         specifier: ^4.17.21
         version: 4.17.21
@@ -683,9 +692,6 @@ importers:
       react-intersection-observer:
         specifier: ^9.13.0
         version: 9.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      react-oidc-context:
-        specifier: ^3.1.0
-        version: 3.1.0(oidc-client-ts@3.0.1)(react@18.3.1)
       react-router-dom:
         specifier: ^6.26.1
         version: 6.26.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -892,6 +898,9 @@ importers:
       react-intersection-observer:
         specifier: ^9.13.0
         version: 9.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      react-oidc-context:
+        specifier: ^3.2.0
+        version: 3.2.0(oidc-client-ts@3.0.1)(react@18.3.1)
       react-plotly.js:
         specifier: ^2.6.0
         version: 2.6.0(plotly.js-dist-min@2.34.0)(react@18.3.1)
@@ -4676,11 +4685,11 @@ packages:
       react: '>0.13.0'
       react-dom: '>0.13.0'
 
-  react-oidc-context@3.1.0:
-    resolution: {integrity: sha512-ceQztvDfdl28mbr0So31XF/tCJamyF1+nm4AQNIE/nub+Xs9PLtDqLy/+75Yx1ahI0/n3nsq0R2qcP0R2Laa3Q==}
+  react-oidc-context@3.2.0:
+    resolution: {integrity: sha512-ZLaCRLWV84Cn9pFdsatmblqxLMv0np69GWVXq9RWGqAjppdOGXNIbIxWMByIio0oSCVUwdeqwYRnJme0tjqd8A==}
     engines: {node: '>=18'}
     peerDependencies:
-      oidc-client-ts: ^3.0.0
+      oidc-client-ts: ^3.1.0
       react: '>=16.8.0'
 
   react-onclickoutside@6.13.1:
@@ -9829,7 +9838,7 @@ snapshots:
       react-dom: 18.3.1(react@18.3.1)
       resize-observer-polyfill: 1.5.1
 
-  react-oidc-context@3.1.0(oidc-client-ts@3.0.1)(react@18.3.1):
+  react-oidc-context@3.2.0(oidc-client-ts@3.0.1)(react@18.3.1):
     dependencies:
       oidc-client-ts: 3.0.1
       react: 18.3.1