From b9103e8d229b120bbb596e45e8992fba9b44ccd1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tadeusz=20Miesi=C4=85c?= <tadeusz.miesiac@gmail.com>
Date: Wed, 24 Jan 2024 13:08:05 +0100
Subject: [PATCH] feat(publications): initialised modal and fetching
 publications

---
 package-lock.json                             | 45 +++++++++++++++++++
 package.json                                  |  1 +
 .../FunctionalArea/Modal/Modal.component.tsx  |  2 +
 .../PublicationsModal.test.tsx                | 20 +++++++++
 .../PublicationsModal/PublicationsModal.tsx   | 13 ++++++
 .../Modal/PublicationsModal/index.ts          |  1 +
 .../ProjectInfoDrawer.component.tsx           | 24 ++++++++--
 src/models/publicationsResponseSchema.ts      | 10 +++++
 src/models/publicationsSchema.ts              | 10 +++++
 src/redux/apiPath.ts                          | 31 +++++++++++++
 src/redux/modal/modal.reducers.ts             |  6 +++
 src/redux/modal/modal.slice.ts                |  3 ++
 src/redux/publications/publications.mock.ts   |  7 +++
 .../publications/publications.reducers.ts     | 19 ++++++++
 .../publications/publications.selectors.ts    | 16 +++++++
 src/redux/publications/publications.slice.ts  | 20 +++++++++
 src/redux/publications/publications.thunks.ts | 17 +++++++
 src/redux/publications/publications.types.ts  | 13 ++++++
 src/redux/root/root.fixtures.ts               |  2 +
 src/redux/store.ts                            |  2 +
 src/types/modal.ts                            |  2 +-
 src/types/models.ts                           |  6 ++-
 22 files changed, 264 insertions(+), 6 deletions(-)
 create mode 100644 src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModal.test.tsx
 create mode 100644 src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModal.tsx
 create mode 100644 src/components/FunctionalArea/Modal/PublicationsModal/index.ts
 create mode 100644 src/models/publicationsResponseSchema.ts
 create mode 100644 src/models/publicationsSchema.ts
 create mode 100644 src/redux/publications/publications.mock.ts
 create mode 100644 src/redux/publications/publications.reducers.ts
 create mode 100644 src/redux/publications/publications.selectors.ts
 create mode 100644 src/redux/publications/publications.slice.ts
 create mode 100644 src/redux/publications/publications.thunks.ts
 create mode 100644 src/redux/publications/publications.types.ts

diff --git a/package-lock.json b/package-lock.json
index 4aad8440..fda23e3b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,7 @@
       "dependencies": {
         "@next/font": "^13.5.2",
         "@reduxjs/toolkit": "^1.9.6",
+        "@tanstack/react-table": "^8.11.7",
         "@types/node": "20.6.2",
         "@types/openlayers": "^4.6.20",
         "@types/react": "18.2.21",
@@ -2018,6 +2019,37 @@
         "tslib": "^2.4.0"
       }
     },
+    "node_modules/@tanstack/react-table": {
+      "version": "8.11.7",
+      "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.11.7.tgz",
+      "integrity": "sha512-ZbzfMkLjxUTzNPBXJYH38pv2VpC9WUA+Qe5USSHEBz0dysDTv4z/ARI3csOed/5gmlmrPzVUN3UXGuUMbod3Jg==",
+      "dependencies": {
+        "@tanstack/table-core": "8.11.7"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/tannerlinsley"
+      },
+      "peerDependencies": {
+        "react": ">=16",
+        "react-dom": ">=16"
+      }
+    },
+    "node_modules/@tanstack/table-core": {
+      "version": "8.11.7",
+      "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.11.7.tgz",
+      "integrity": "sha512-N3ksnkbPbsF3PjubuZCB/etTqvctpXWRHIXTmYfJFnhynQKjeZu8BCuHvdlLPpumKbA+bjY4Ay9AELYLOXPWBg==",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/tannerlinsley"
+      }
+    },
     "node_modules/@testing-library/dom": {
       "version": "9.3.3",
       "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.3.tgz",
@@ -15341,6 +15373,19 @@
         "tslib": "^2.4.0"
       }
     },
+    "@tanstack/react-table": {
+      "version": "8.11.7",
+      "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.11.7.tgz",
+      "integrity": "sha512-ZbzfMkLjxUTzNPBXJYH38pv2VpC9WUA+Qe5USSHEBz0dysDTv4z/ARI3csOed/5gmlmrPzVUN3UXGuUMbod3Jg==",
+      "requires": {
+        "@tanstack/table-core": "8.11.7"
+      }
+    },
+    "@tanstack/table-core": {
+      "version": "8.11.7",
+      "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.11.7.tgz",
+      "integrity": "sha512-N3ksnkbPbsF3PjubuZCB/etTqvctpXWRHIXTmYfJFnhynQKjeZu8BCuHvdlLPpumKbA+bjY4Ay9AELYLOXPWBg=="
+    },
     "@testing-library/dom": {
       "version": "9.3.3",
       "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.3.tgz",
diff --git a/package.json b/package.json
index 4cf34d33..e45e31b6 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
   "dependencies": {
     "@next/font": "^13.5.2",
     "@reduxjs/toolkit": "^1.9.6",
+    "@tanstack/react-table": "^8.11.7",
     "@types/node": "20.6.2",
     "@types/openlayers": "^4.6.20",
     "@types/react": "18.2.21",
diff --git a/src/components/FunctionalArea/Modal/Modal.component.tsx b/src/components/FunctionalArea/Modal/Modal.component.tsx
index 0244ce42..f8f68474 100644
--- a/src/components/FunctionalArea/Modal/Modal.component.tsx
+++ b/src/components/FunctionalArea/Modal/Modal.component.tsx
@@ -8,6 +8,7 @@ import { twMerge } from 'tailwind-merge';
 import { LoginModal } from './LoginModal';
 import { MODAL_ROLE } from './Modal.constants';
 import { OverviewImagesModal } from './OverviewImagesModal';
+import { PublicationsModal } from './PublicationsModal';
 
 const MolArtModal = dynamic(
   () => import('./MolArtModal/MolArtModal.component').then(mod => mod.MolArtModal),
@@ -46,6 +47,7 @@ export const Modal = (): React.ReactNode => {
           {isOpen && modalName === 'overview-images' && <OverviewImagesModal />}
           {isOpen && modalName === 'mol-art' && <MolArtModal />}
           {isOpen && modalName === 'login' && <LoginModal />}
+          {isOpen && modalName === 'publications' && <PublicationsModal />}
         </div>
       </div>
     </div>
diff --git a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModal.test.tsx b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModal.test.tsx
new file mode 100644
index 00000000..4da37a1f
--- /dev/null
+++ b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModal.test.tsx
@@ -0,0 +1,20 @@
+describe('Publications Modal - component', () => {
+  it('should render number of publications', () => {});
+  it('should render download csv button', () => {});
+  it('should trigger download on csv button click', () => {});
+  it('should render search input', () => {});
+  it('should be able to search publications by using search input', () => {});
+  it('should be able to sort publications by clicking on Pubmed ID column header', () => {});
+  it('should be able to sort publications by clicking on Title column header', () => {});
+  it('should be able to sort publications by clicking on Authors column header', () => {});
+  it('should be able to sort publications by clicking on Journal column header', () => {});
+  it('should be able to sort publications by clicking on Year column header', () => {});
+  it('should be able to sort publications by clicking on Elements on map column header', () => {});
+  it('should be able to sort publications by clicking on SUBMAPS on map column header', () => {});
+
+  it('should render publications list', () => {});
+
+  it('should render pagination', () => {});
+  it('should be able to navigate to next page', () => {});
+  it('should be able to navigate to previous page', () => {});
+});
diff --git a/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModal.tsx b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModal.tsx
new file mode 100644
index 00000000..9c3c6a9d
--- /dev/null
+++ b/src/components/FunctionalArea/Modal/PublicationsModal/PublicationsModal.tsx
@@ -0,0 +1,13 @@
+import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
+import { getPublications } from '@/redux/publications/publications.thunks';
+import { useEffect } from 'react';
+
+export const PublicationsModal = (): JSX.Element => {
+  const dispatch = useAppDispatch();
+
+  useEffect(() => {
+    dispatch(getPublications({}));
+  }, [dispatch]);
+
+  return <div className="flex h-full w-full items-center justify-center bg-white">lol</div>;
+};
diff --git a/src/components/FunctionalArea/Modal/PublicationsModal/index.ts b/src/components/FunctionalArea/Modal/PublicationsModal/index.ts
new file mode 100644
index 00000000..ea5f74a5
--- /dev/null
+++ b/src/components/FunctionalArea/Modal/PublicationsModal/index.ts
@@ -0,0 +1 @@
+export { PublicationsModal } from './PublicationsModal';
diff --git a/src/components/Map/Drawer/ProjectInfoDrawer/ProjectInfoDrawer.component.tsx b/src/components/Map/Drawer/ProjectInfoDrawer/ProjectInfoDrawer.component.tsx
index 31823981..a006e829 100644
--- a/src/components/Map/Drawer/ProjectInfoDrawer/ProjectInfoDrawer.component.tsx
+++ b/src/components/Map/Drawer/ProjectInfoDrawer/ProjectInfoDrawer.component.tsx
@@ -12,8 +12,12 @@ import { apiPath } from '@/redux/apiPath';
 import { LinkButton } from '@/shared/LinkButton';
 import { mainMapModelDescriptionSelector } from '@/redux/models/models.selectors';
 import './ProjectInfoDrawer.styles.css';
+import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
+import { useEffect } from 'react';
+import { openPublicationsModal } from '@/redux/modal/modal.slice';
 
 export const ProjectInfoDrawer = (): JSX.Element => {
+  const dispatch = useAppDispatch();
   const diseaseName = useAppSelector(diseaseNameSelector);
   const diseaseLink = useAppSelector(diseaseLinkSelector);
   const organismLink = useAppSelector(organismLinkSelector);
@@ -24,6 +28,14 @@ export const ProjectInfoDrawer = (): JSX.Element => {
 
   const sourceDownloadLink = window.location.hostname + apiPath.getSourceFile();
 
+  useEffect(() => {
+    // dispatch(getPublications());
+  }, [dispatch]);
+
+  const onPublicationsClick = (): void => {
+    dispatch(openPublicationsModal());
+  };
+
   return (
     <div data-testid="export-drawer" className="h-full max-h-full">
       <DrawerHeading title="Project info" />
@@ -36,13 +48,17 @@ export const ProjectInfoDrawer = (): JSX.Element => {
         </p>
         <div className="mt-4">Data:</div>
         <ul className="list-disc pl-6 ">
-          <li className="mt-2 text-hyperlink-blue">(21) publications</li>
+          <li className="mt-2 text-hyperlink-blue">
+            <button type="button" onClick={onPublicationsClick} className="text-sm font-semibold">
+              (21) publications
+            </button>
+          </li>
           <li className="mt-2 text-hyperlink-blue">
             <a
               href="https://minerva.pages.uni.lu/doc/"
               target="_blank"
               rel="noopener noreferrer"
-              className="hover:underline"
+              className="font-semibold hover:underline"
             >
               Manual
             </a>
@@ -53,7 +69,7 @@ export const ProjectInfoDrawer = (): JSX.Element => {
               href={diseaseLink}
               target="_blank"
               rel="noopener noreferrer"
-              className="hover:underline"
+              className="font-semibold hover:underline"
             >
               {diseaseName}
             </a>
@@ -64,7 +80,7 @@ export const ProjectInfoDrawer = (): JSX.Element => {
               href={organismLink}
               target="_blank"
               rel="noopener noreferrer"
-              className="hover:underline"
+              className="font-semibold hover:underline"
             >
               {organismName}
             </a>
diff --git a/src/models/publicationsResponseSchema.ts b/src/models/publicationsResponseSchema.ts
new file mode 100644
index 00000000..5bdcca5b
--- /dev/null
+++ b/src/models/publicationsResponseSchema.ts
@@ -0,0 +1,10 @@
+import { z } from 'zod';
+import { publicationSchema } from './publicationsSchema';
+
+export const publicationsResponseSchema = z.object({
+  data: z.array(publicationSchema),
+  totalSize: z.number(),
+  filteredSize: z.number(),
+  length: z.number(),
+  page: z.number(),
+});
diff --git a/src/models/publicationsSchema.ts b/src/models/publicationsSchema.ts
new file mode 100644
index 00000000..b1574088
--- /dev/null
+++ b/src/models/publicationsSchema.ts
@@ -0,0 +1,10 @@
+import { z } from 'zod';
+import { targetElementSchema } from './targetElementSchema';
+import { articleSchema } from './articleSchema';
+
+export const publicationSchema = z.object({
+  elements: z.array(targetElementSchema),
+  publication: z.object({
+    article: articleSchema,
+  }),
+});
diff --git a/src/redux/apiPath.ts b/src/redux/apiPath.ts
index a3e416a4..95aed095 100644
--- a/src/redux/apiPath.ts
+++ b/src/redux/apiPath.ts
@@ -2,6 +2,33 @@ import { PROJECT_ID } from '@/constants';
 import { PerfectSearchParams } from '@/types/search';
 import { Point } from '@/types/map';
 
+export type GetPublicationsParams = {
+  start?: number;
+  sortColumn?: string;
+  sortOrder?: string;
+  level?: number;
+  length?: number;
+  search?: string;
+};
+
+const getPublicationsURLSearchParams = ({
+  start,
+  sortColumn,
+  sortOrder,
+  level,
+  length,
+  search,
+}: GetPublicationsParams): URLSearchParams => {
+  const params = new URLSearchParams();
+  if (start) params.append('start', start.toString());
+  if (sortColumn) params.append('sortColumn', sortColumn);
+  if (sortOrder) params.append('sortOrder', sortOrder);
+  if (level) params.append('level', level.toString());
+  if (length) params.append('length', length.toString());
+  if (search) params.append('search', search);
+  return params;
+};
+
 export const apiPath = {
   getBioEntityContentsStringWithQuery: ({
     searchQuery,
@@ -47,4 +74,8 @@ export const apiPath = {
   getSourceFile: (): string => `/projects/${PROJECT_ID}:downloadSource`,
   getMesh: (meshId: string): string => `mesh/${meshId}`,
   getTaxonomy: (taxonomyId: string): string => `taxonomy/${taxonomyId}`,
+  getPublications: (params: GetPublicationsParams, modelId = '*'): string =>
+    `/projects/${PROJECT_ID}/models/${modelId}/publications/${getPublicationsURLSearchParams(
+      params,
+    )}`,
 };
diff --git a/src/redux/modal/modal.reducers.ts b/src/redux/modal/modal.reducers.ts
index 78d5adea..7b158180 100644
--- a/src/redux/modal/modal.reducers.ts
+++ b/src/redux/modal/modal.reducers.ts
@@ -50,3 +50,9 @@ export const setOverviewImageIdReducer = (
     imageId: action.payload,
   };
 };
+
+export const openPublicationsModalReducer = (state: ModalState): void => {
+  state.isOpen = true;
+  state.modalName = 'publications';
+  state.modalTitle = 'Publications';
+};
diff --git a/src/redux/modal/modal.slice.ts b/src/redux/modal/modal.slice.ts
index 84f10231..a324db23 100644
--- a/src/redux/modal/modal.slice.ts
+++ b/src/redux/modal/modal.slice.ts
@@ -7,6 +7,7 @@ import {
   openOverviewImagesModalByIdReducer,
   openMolArtModalByIdReducer,
   setOverviewImageIdReducer,
+  openPublicationsModalReducer,
 } from './modal.reducers';
 
 const modalSlice = createSlice({
@@ -19,6 +20,7 @@ const modalSlice = createSlice({
     openMolArtModalById: openMolArtModalByIdReducer,
     setOverviewImageId: setOverviewImageIdReducer,
     openLoginModal: openLoginModalReducer,
+    openPublicationsModal: openPublicationsModalReducer,
   },
 });
 
@@ -29,6 +31,7 @@ export const {
   setOverviewImageId,
   openMolArtModalById,
   openLoginModal,
+  openPublicationsModal,
 } = modalSlice.actions;
 
 export default modalSlice.reducer;
diff --git a/src/redux/publications/publications.mock.ts b/src/redux/publications/publications.mock.ts
new file mode 100644
index 00000000..ea202e14
--- /dev/null
+++ b/src/redux/publications/publications.mock.ts
@@ -0,0 +1,7 @@
+import { PublicationsState } from './publications.types';
+
+export const PUBLICATIONS_INITIAL_STATE_MOCK: PublicationsState = {
+  loading: 'idle',
+  data: undefined,
+  error: { name: '', message: '' },
+};
diff --git a/src/redux/publications/publications.reducers.ts b/src/redux/publications/publications.reducers.ts
new file mode 100644
index 00000000..ed3fc057
--- /dev/null
+++ b/src/redux/publications/publications.reducers.ts
@@ -0,0 +1,19 @@
+import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
+import { PublicationsState } from './publications.types';
+import { getPublications } from './publications.thunks';
+
+export const getPublicationsReducer = (
+  builder: ActionReducerMapBuilder<PublicationsState>,
+): void => {
+  builder.addCase(getPublications.pending, state => {
+    state.loading = 'pending';
+  });
+  builder.addCase(getPublications.fulfilled, (state, action) => {
+    state.data = action.payload || undefined;
+    state.loading = 'succeeded';
+  });
+  builder.addCase(getPublications.rejected, state => {
+    state.loading = 'failed';
+    // TODO to discuss manage state of failure
+  });
+};
diff --git a/src/redux/publications/publications.selectors.ts b/src/redux/publications/publications.selectors.ts
new file mode 100644
index 00000000..a1fcbb1c
--- /dev/null
+++ b/src/redux/publications/publications.selectors.ts
@@ -0,0 +1,16 @@
+import { createSelector } from '@reduxjs/toolkit';
+import { rootSelector } from '../root/root.selectors';
+
+export const publicationsSelector = createSelector(rootSelector, state => state.publications);
+
+export const publicationsDataSelector = createSelector(
+  publicationsSelector,
+  publications => publications?.data,
+);
+
+export const totalSizeSelector = createSelector(publicationsDataSelector, data => data?.totalSize);
+
+export const filteredSizeSelector = createSelector(
+  publicationsDataSelector,
+  data => data?.filteredSize,
+);
diff --git a/src/redux/publications/publications.slice.ts b/src/redux/publications/publications.slice.ts
new file mode 100644
index 00000000..29189345
--- /dev/null
+++ b/src/redux/publications/publications.slice.ts
@@ -0,0 +1,20 @@
+import { createSlice } from '@reduxjs/toolkit';
+import { PublicationsState } from './publications.types';
+import { getPublicationsReducer } from './publications.reducers';
+
+const initialState: PublicationsState = {
+  data: undefined,
+  loading: 'idle',
+  error: { name: '', message: '' },
+};
+
+const publicationsSlice = createSlice({
+  name: 'publications',
+  initialState,
+  reducers: {},
+  extraReducers: builder => {
+    getPublicationsReducer(builder);
+  },
+});
+
+export default publicationsSlice.reducer;
diff --git a/src/redux/publications/publications.thunks.ts b/src/redux/publications/publications.thunks.ts
new file mode 100644
index 00000000..cb39b53f
--- /dev/null
+++ b/src/redux/publications/publications.thunks.ts
@@ -0,0 +1,17 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
+import { axiosInstance } from '@/services/api/utils/axiosInstance';
+import { PublicationsResponse } from '@/types/models';
+import { publicationsResponseSchema } from '@/models/publicationsResponseSchema';
+import { GetPublicationsParams, apiPath } from '../apiPath';
+
+export const getPublications = createAsyncThunk(
+  'publications/getPublications',
+  async (params: GetPublicationsParams): Promise<PublicationsResponse | undefined> => {
+    const response = await axiosInstance.get<PublicationsResponse>(apiPath.getPublications(params));
+
+    const isDataValid = validateDataUsingZodSchema(response.data, publicationsResponseSchema);
+
+    return isDataValid ? response.data : undefined;
+  },
+);
diff --git a/src/redux/publications/publications.types.ts b/src/redux/publications/publications.types.ts
new file mode 100644
index 00000000..9a6875aa
--- /dev/null
+++ b/src/redux/publications/publications.types.ts
@@ -0,0 +1,13 @@
+import { FetchDataState } from '@/types/fetchDataState';
+import { PublicationsResponse } from '@/types/models';
+
+export type PublicationsState = FetchDataState<PublicationsResponse>;
+
+export type GetPublicationsParams = {
+  start?: number;
+  sortColumn?: string;
+  sortOrder?: string;
+  level?: number;
+  length?: number;
+  search?: string;
+};
diff --git a/src/redux/root/root.fixtures.ts b/src/redux/root/root.fixtures.ts
index a8494c46..77d70737 100644
--- a/src/redux/root/root.fixtures.ts
+++ b/src/redux/root/root.fixtures.ts
@@ -19,6 +19,7 @@ import { RootState } from '../store';
 import { USER_INITIAL_STATE_MOCK } from '../user/user.mock';
 import { STATISTICS_STATE_INITIAL_MOCK } from '../statistics/statistics.mock';
 import { COMPARTMENT_PATHWAYS_INITIAL_STATE_MOCK } from '../compartmentPathways/compartmentPathways.mock';
+import { PUBLICATIONS_INITIAL_STATE_MOCK } from '../publications/publications.mock';
 
 export const INITIAL_STORE_STATE_MOCK: RootState = {
   search: SEARCH_STATE_INITIAL_MOCK,
@@ -41,4 +42,5 @@ export const INITIAL_STORE_STATE_MOCK: RootState = {
   legend: LEGEND_INITIAL_STATE_MOCK,
   statistics: STATISTICS_STATE_INITIAL_MOCK,
   compartmentPathways: COMPARTMENT_PATHWAYS_INITIAL_STATE_MOCK,
+  publications: PUBLICATIONS_INITIAL_STATE_MOCK,
 };
diff --git a/src/redux/store.ts b/src/redux/store.ts
index 944288e8..32c4c738 100644
--- a/src/redux/store.ts
+++ b/src/redux/store.ts
@@ -26,6 +26,7 @@ import legendReducer from './legend/legend.slice';
 import { mapListenerMiddleware } from './map/middleware/map.middleware';
 import statisticsReducer from './statistics/statistics.slice';
 import compartmentPathwaysReducer from './compartmentPathways/compartmentPathways.slice';
+import publicationsReducer from './publications/publications.slice';
 
 export const reducers = {
   search: searchReducer,
@@ -48,6 +49,7 @@ export const reducers = {
   legend: legendReducer,
   statistics: statisticsReducer,
   compartmentPathways: compartmentPathwaysReducer,
+  publications: publicationsReducer,
 };
 
 export const middlewares = [mapListenerMiddleware.middleware];
diff --git a/src/types/modal.ts b/src/types/modal.ts
index 76392693..474c0f7a 100644
--- a/src/types/modal.ts
+++ b/src/types/modal.ts
@@ -1 +1 @@
-export type ModalName = 'none' | 'overview-images' | 'mol-art' | 'login';
+export type ModalName = 'none' | 'overview-images' | 'mol-art' | 'login' | 'publications';
diff --git a/src/types/models.ts b/src/types/models.ts
index 8ad656d4..22862e3a 100644
--- a/src/types/models.ts
+++ b/src/types/models.ts
@@ -1,3 +1,4 @@
+import { z } from 'zod';
 import { bioEntityContentSchema } from '@/models/bioEntityContentSchema';
 import { bioEntityResponseSchema } from '@/models/bioEntityResponseSchema';
 import { bioEntitySchema } from '@/models/bioEntitySchema';
@@ -30,13 +31,14 @@ import {
 } from '@/models/overviewImageLink';
 import { overviewImageView } from '@/models/overviewImageView';
 import { projectSchema } from '@/models/projectSchema';
+import { publicationSchema } from '@/models/publicationsSchema';
 import { reactionSchema } from '@/models/reaction';
 import { reactionLineSchema } from '@/models/reactionLineSchema';
 import { referenceSchema } from '@/models/referenceSchema';
 import { sessionSchemaValid } from '@/models/sessionValidSchema';
 import { statisticsSchema } from '@/models/statisticsSchema';
 import { targetSchema } from '@/models/targetSchema';
-import { z } from 'zod';
+import { publicationsResponseSchema } from '@/models/publicationsResponseSchema';
 
 export type Project = z.infer<typeof projectSchema>;
 export type OverviewImageView = z.infer<typeof overviewImageView>;
@@ -72,3 +74,5 @@ export type Color = z.infer<typeof colorSchema>;
 export type Statistics = z.infer<typeof statisticsSchema>;
 export type CompartmentPathway = z.infer<typeof compartmentPathwaySchema>;
 export type CompartmentPathwayDetails = z.infer<typeof compartmentPathwayDetailsSchema>;
+export type PublicationsResponse = z.infer<typeof publicationsResponseSchema>;
+export type Publication = z.infer<typeof publicationSchema>;
-- 
GitLab