From 5482104312ce3e6b6974a1ef206c05e51b194630 Mon Sep 17 00:00:00 2001
From: mateusz-winiarczyk <mateusz.winiarczyk@appunite.com>
Date: Wed, 27 Mar 2024 15:03:02 +0100
Subject: [PATCH] feat(project): deploy and project adjustments (MIN-304)

---
 .env                                          |  5 ---
 index.d.ts                                    |  7 ++++
 next.config.js                                |  8 ++---
 pages/_document.tsx                           | 15 +++++++++
 public/config.js                              |  6 ++++
 setupTests.ts                                 |  9 +++++
 .../OverviewImagesModal.component.test.tsx    |  2 ++
 .../utils/useOverviewImage.test.ts            |  3 ++
 .../utils/useOverviewImageLinkActions.test.ts |  8 +++++
 .../useOverviewImageLinkElements.test.ts      |  3 ++
 .../utils/useOverviewImageSize.test.tsx       |  3 ++
 .../utils/useOverviewImageUrl.test.ts         |  2 ++
 .../UserOverlayForm.component.test.tsx        |  5 +++
 .../ProjectInfoDrawer.component.test.tsx      |  1 +
 .../MapAdditionalOptions.component.test.tsx   | 28 +++++++++++++++-
 .../MapAdditionalOptions.component.tsx        | 14 ++++++--
 src/constants/index.ts                        | 11 ++++---
 src/constants/index.utils.test.ts             | 33 +++++++++++++++++++
 src/constants/index.utils.ts                  | 19 +++++++++++
 src/models/projectSchema.ts                   |  2 +-
 src/redux/overlays/overlays.thunks.ts         |  3 +-
 src/redux/project/project.mock.ts             |  3 +-
 src/redux/project/project.reducers.test.ts    |  1 +
 src/redux/project/project.reducers.ts         |  8 ++++-
 src/redux/project/project.selectors.ts        |  4 ++-
 src/redux/project/project.slice.ts            |  4 ++-
 src/redux/project/project.thunks.ts           | 11 +++++++
 src/redux/project/project.types.ts            |  4 +++
 src/redux/root/init.thunks.ts                 |  4 ++-
 src/redux/root/query.selectors.ts             |  4 +++
 src/types/query.ts                            |  3 ++
 src/utils/parseQueryToTypes.ts                |  1 +
 .../useReduxBusQueryManager.test.ts           |  1 +
 33 files changed, 211 insertions(+), 24 deletions(-)
 create mode 100644 pages/_document.tsx
 create mode 100644 public/config.js
 create mode 100644 src/constants/index.utils.test.ts
 create mode 100644 src/constants/index.utils.ts

diff --git a/.env b/.env
index b3e48b42..295a27f9 100644
--- a/.env
+++ b/.env
@@ -1,6 +1 @@
-
-NEXT_PUBLIC_BASE_API_URL = 'https://lux1.atcomp.pl/minerva/api'
-NEXT_PUBLIC_BASE_NEW_API_URL = 'https://lux1.atcomp.pl/minerva/new_api/'
-BASE_MAP_IMAGES_URL = 'https://lux1.atcomp.pl/'
-NEXT_PUBLIC_PROJECT_ID = 'pdmap_appu_test'
 ZOD_SEED = 997
diff --git a/index.d.ts b/index.d.ts
index f9baf054..81c13458 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -42,6 +42,13 @@ type HashPlugin = {
 
 declare global {
   interface Window {
+    config: {
+      BASE_API_URL: string;
+      BASE_NEW_API_URL: string;
+      BASE_MAP_IMAGES_URL: string;
+      DEFAULT_PROJECT_ID: string;
+    };
+
     minerva: {
       configuration?: MinervaConfiguration;
       plugins: {
diff --git a/next.config.js b/next.config.js
index eebeaf4d..c451e700 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,13 +1,11 @@
 /** @type {import('next').NextConfig} */
 const nextConfig = {
   reactStrictMode: true,
-  experimental: {
-    fontLoaders: [{ loader: '@next/font/google', options: { subsets: ['latin'] } }],
+  output: 'export',
+  images: {
+    unoptimized: true,
   },
   env: {
-    BASE_API_URL: process.env.NEXT_PUBLIC_BASE_API_URL || '',
-    BASE_MAP_IMAGES_URL: process.env.BASE_MAP_IMAGES_URL || '',
-    PROJECT_ID: process.env.NEXT_PUBLIC_PROJECT_ID || '',
     ZOD_SEED: process.env.ZOD_SEED || 123,
   },
 };
diff --git a/pages/_document.tsx b/pages/_document.tsx
new file mode 100644
index 00000000..eaa2d812
--- /dev/null
+++ b/pages/_document.tsx
@@ -0,0 +1,15 @@
+import { Html, Head, Main, NextScript } from 'next/document';
+import Script from 'next/script';
+
+const Document = (): React.ReactNode => (
+  <Html>
+    <Head />
+    <body>
+      <Main />
+      <NextScript />
+      <Script src="/config.js" strategy="beforeInteractive" />
+    </body>
+  </Html>
+);
+
+export default Document;
diff --git a/public/config.js b/public/config.js
new file mode 100644
index 00000000..c30b99cc
--- /dev/null
+++ b/public/config.js
@@ -0,0 +1,6 @@
+window.config = {
+  BASE_API_URL: 'https://lux1.atcomp.pl/minerva/api',
+  BASE_NEW_API_URL: 'https://lux1.atcomp.pl/minerva/new_api/',
+  BASE_MAP_IMAGES_URL: 'https://lux1.atcomp.pl/',
+  DEFAULT_PROJECT_ID: 'pdmap_appu_test',
+};
diff --git a/setupTests.ts b/setupTests.ts
index 1d944c81..7a4cf03f 100644
--- a/setupTests.ts
+++ b/setupTests.ts
@@ -36,3 +36,12 @@ const localStorageMock = (() => {
 Object.defineProperty(global, 'localStorage', {
   value: localStorageMock,
 });
+
+Object.defineProperty(window, 'config', {
+  value: {
+    BASE_API_URL: 'https://lux1.atcomp.pl/minerva/api',
+    BASE_NEW_API_URL: 'https://lux1.atcomp.pl/minerva/new_api/',
+    BASE_MAP_IMAGES_URL: 'https://lux1.atcomp.pl/',
+    DEFAULT_PROJECT_ID: 'pdmap_appu_test',
+  },
+});
diff --git a/src/components/FunctionalArea/Modal/OverviewImagesModal/OverviewImagesModal.component.test.tsx b/src/components/FunctionalArea/Modal/OverviewImagesModal/OverviewImagesModal.component.test.tsx
index b8c92be9..5b7ed93e 100644
--- a/src/components/FunctionalArea/Modal/OverviewImagesModal/OverviewImagesModal.component.test.tsx
+++ b/src/components/FunctionalArea/Modal/OverviewImagesModal/OverviewImagesModal.component.test.tsx
@@ -45,6 +45,7 @@ describe('OverviewImagesModal - component', () => {
           },
           loading: 'succeeded',
           error: { message: '', name: '' },
+          projectId: '',
         },
         modal: {
           ...MODAL_INITIAL_STATE_MOCK,
@@ -72,6 +73,7 @@ describe('OverviewImagesModal - component', () => {
           },
           loading: 'succeeded',
           error: { message: '', name: '' },
+          projectId: '',
         },
         modal: {
           ...MODAL_INITIAL_STATE_MOCK,
diff --git a/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImage.test.ts b/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImage.test.ts
index cd7468fd..f5ae9c32 100644
--- a/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImage.test.ts
+++ b/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImage.test.ts
@@ -18,6 +18,7 @@ describe('useOverviewImage - hook', () => {
         },
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       modal: {
         ...MODAL_INITIAL_STATE_MOCK,
@@ -50,6 +51,7 @@ describe('useOverviewImage - hook', () => {
         },
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       modal: {
         ...MODAL_INITIAL_STATE_MOCK,
@@ -89,6 +91,7 @@ describe('useOverviewImage - hook', () => {
         },
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       modal: {
         ...MODAL_INITIAL_STATE_MOCK,
diff --git a/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageLinkActions.test.ts b/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageLinkActions.test.ts
index 609cc0f5..375cd187 100644
--- a/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageLinkActions.test.ts
+++ b/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageLinkActions.test.ts
@@ -43,6 +43,7 @@ describe('useOverviewImageLinkActions - hook', () => {
           },
           loading: 'succeeded',
           error: { message: '', name: '' },
+          projectId: '',
         },
         modal: {
           ...MODAL_INITIAL_STATE_MOCK,
@@ -92,6 +93,7 @@ describe('useOverviewImageLinkActions - hook', () => {
           },
           loading: 'succeeded',
           error: { message: '', name: '' },
+          projectId: '',
         },
         modal: {
           ...MODAL_INITIAL_STATE_MOCK,
@@ -148,6 +150,7 @@ describe('useOverviewImageLinkActions - hook', () => {
             },
             loading: 'succeeded',
             error: { message: '', name: '' },
+            projectId: '',
           },
           modal: {
             ...MODAL_INITIAL_STATE_MOCK,
@@ -225,6 +228,7 @@ describe('useOverviewImageLinkActions - hook', () => {
             },
             loading: 'succeeded',
             error: { message: '', name: '' },
+            projectId: '',
           },
           modal: {
             ...MODAL_INITIAL_STATE_MOCK,
@@ -328,6 +332,7 @@ describe('useOverviewImageLinkActions - hook', () => {
           },
           loading: 'succeeded',
           error: { message: '', name: '' },
+          projectId: '',
         },
         modal: {
           ...MODAL_INITIAL_STATE_MOCK,
@@ -378,6 +383,7 @@ describe('useOverviewImageLinkActions - hook', () => {
           },
           loading: 'succeeded',
           error: { message: '', name: '' },
+          projectId: '',
         },
         map: {
           data: {
@@ -438,6 +444,7 @@ describe('useOverviewImageLinkActions - hook', () => {
           },
           loading: 'succeeded',
           error: { message: '', name: '' },
+          projectId: '',
         },
         map: {
           data: {
@@ -500,6 +507,7 @@ describe('useOverviewImageLinkActions - hook', () => {
           },
           loading: 'succeeded',
           error: { message: '', name: '' },
+          projectId: '',
         },
         map: {
           data: {
diff --git a/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageLinkElements.test.ts b/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageLinkElements.test.ts
index 308e5240..61169d09 100644
--- a/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageLinkElements.test.ts
+++ b/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageLinkElements.test.ts
@@ -17,6 +17,7 @@ describe('useOverviewImageLinkConfigs - hook', () => {
         },
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       modal: {
         ...MODAL_INITIAL_STATE_MOCK,
@@ -53,6 +54,7 @@ describe('useOverviewImageLinkConfigs - hook', () => {
         },
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       modal: {
         ...MODAL_INITIAL_STATE_MOCK,
@@ -89,6 +91,7 @@ describe('useOverviewImageLinkConfigs - hook', () => {
         },
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       modal: {
         ...MODAL_INITIAL_STATE_MOCK,
diff --git a/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageSize.test.tsx b/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageSize.test.tsx
index b5c8b6b5..f1cfe2e7 100644
--- a/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageSize.test.tsx
+++ b/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageSize.test.tsx
@@ -18,6 +18,7 @@ describe('useOverviewImageSize - hook', () => {
         },
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       modal: {
         ...MODAL_INITIAL_STATE_MOCK,
@@ -49,6 +50,7 @@ describe('useOverviewImageSize - hook', () => {
         },
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       modal: {
         ...MODAL_INITIAL_STATE_MOCK,
@@ -77,6 +79,7 @@ describe('useOverviewImageSize - hook', () => {
         },
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       modal: {
         ...MODAL_INITIAL_STATE_MOCK,
diff --git a/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageUrl.test.ts b/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageUrl.test.ts
index b5e9beb8..b257be4c 100644
--- a/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageUrl.test.ts
+++ b/src/components/FunctionalArea/Modal/OverviewImagesModal/utils/useOverviewImageUrl.test.ts
@@ -17,6 +17,7 @@ describe('useOverviewImageUrl - hook', () => {
         },
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       modal: {
         ...MODAL_INITIAL_STATE_MOCK,
@@ -43,6 +44,7 @@ describe('useOverviewImageUrl - hook', () => {
         },
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       modal: {
         ...MODAL_INITIAL_STATE_MOCK,
diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.component.test.tsx b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.component.test.tsx
index b9966147..0d0561aa 100644
--- a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.component.test.tsx
+++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.component.test.tsx
@@ -88,6 +88,7 @@ describe('UserOverlayForm - Component', () => {
         data: projectFixture,
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       overlays: OVERLAYS_INITIAL_STATE_MOCK,
     });
@@ -111,6 +112,7 @@ describe('UserOverlayForm - Component', () => {
         data: projectFixture,
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       overlays: OVERLAYS_INITIAL_STATE_MOCK,
     });
@@ -181,6 +183,7 @@ describe('UserOverlayForm - Component', () => {
         data: projectFixture,
         loading: 'succeeded',
         error: { message: '', name: '' },
+        projectId: '',
       },
       overlays: OVERLAYS_INITIAL_STATE_MOCK,
     });
@@ -225,6 +228,7 @@ describe('UserOverlayForm - Component', () => {
         data: projectFixture,
         loading: 'succeeded',
         error: DEFAULT_ERROR,
+        projectId: '',
       },
       overlays: OVERLAYS_INITIAL_STATE_MOCK,
     });
@@ -283,6 +287,7 @@ describe('UserOverlayForm - Component', () => {
         data: projectFixture,
         loading: 'succeeded',
         error: DEFAULT_ERROR,
+        projectId: '',
       },
       overlays: OVERLAYS_INITIAL_STATE_MOCK,
     });
diff --git a/src/components/Map/Drawer/ProjectInfoDrawer/ProjectInfoDrawer.component.test.tsx b/src/components/Map/Drawer/ProjectInfoDrawer/ProjectInfoDrawer.component.test.tsx
index bfad3705..e7e1a3ef 100644
--- a/src/components/Map/Drawer/ProjectInfoDrawer/ProjectInfoDrawer.component.test.tsx
+++ b/src/components/Map/Drawer/ProjectInfoDrawer/ProjectInfoDrawer.component.test.tsx
@@ -14,6 +14,7 @@ const MOCKED_STORE: InitialStoreState = {
     data: { ...projectFixture },
     loading: 'idle',
     error: new Error(),
+    projectId: '',
   },
   models: {
     data: [MODEL_WITH_DESCRIPTION],
diff --git a/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.test.tsx b/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.test.tsx
index 19571c28..ff8459b4 100644
--- a/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.test.tsx
+++ b/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.test.tsx
@@ -12,6 +12,8 @@ import {
 } from '@/utils/testing/getReduxWrapperWithStore';
 import { render, screen } from '@testing-library/react';
 import { MockStoreEnhanced } from 'redux-mock-store';
+import { PROJECT_STATE_INITIAL_MOCK } from '@/redux/project/project.mock';
+import { projectFixture } from '@/models/fixtures/projectFixture';
 import { MapAdditionalOptions } from './MapAdditionalOptions.component';
 
 const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => {
@@ -59,6 +61,10 @@ describe('MapAdditionalOptions - component', () => {
 
   it('should open overview image modal on button click', () => {
     const { store } = renderComponentWithActionListener({
+      project: {
+        ...PROJECT_STATE_INITIAL_MOCK,
+        data: projectFixture,
+      },
       map: initialMapStateFixture,
       backgrounds: { ...BACKGROUND_INITIAL_STATE_MOCK, data: BACKGROUNDS_MOCK },
     });
@@ -68,8 +74,28 @@ describe('MapAdditionalOptions - component', () => {
 
     const actions = store.getActions();
     expect(actions[FIRST_ARRAY_ELEMENT]).toStrictEqual({
-      payload: 0,
+      payload: projectFixture.topOverviewImage?.idObject,
       type: 'modal/openOverviewImagesModalById',
     });
   });
+  it('should disable button browse overview images if there are no overview images', () => {
+    const { store } = renderComponentWithActionListener({
+      project: {
+        ...PROJECT_STATE_INITIAL_MOCK,
+        data: {
+          ...projectFixture,
+          overviewImageViews: [],
+        },
+      },
+      map: initialMapStateFixture,
+      backgrounds: { ...BACKGROUND_INITIAL_STATE_MOCK, data: BACKGROUNDS_MOCK },
+    });
+
+    const overviewImageButton = screen.getByText('Browse overview images');
+    expect(overviewImageButton).toBeDisabled();
+    overviewImageButton.click();
+
+    const actions = store.getActions();
+    expect(actions).toStrictEqual([]);
+  });
 });
diff --git a/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.tsx b/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.tsx
index 84dec36a..a06ecbdb 100644
--- a/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.tsx
+++ b/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.tsx
@@ -1,9 +1,13 @@
 import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
 import { openOverviewImagesModalById } from '@/redux/modal/modal.slice';
-import { projectDefaultOverviewImageIdSelector } from '@/redux/project/project.selectors';
+import {
+  projectDefaultOverviewImageIdSelector,
+  projectOverviewImagesSelector,
+} from '@/redux/project/project.selectors';
 import { Button } from '@/shared/Button';
 import { useSelector } from 'react-redux';
 import { twMerge } from 'tailwind-merge';
+import { ZERO } from '@/constants/common';
 import { BackgroundSelector } from './BackgroundsSelector';
 
 // top-[calc(64px+40px+24px)] -> TOP_BAR_HEIGHT+MAP_NAVIGATION_HEIGHT+DISTANCE_FROM_MAP_NAVIGATION
@@ -11,6 +15,8 @@ import { BackgroundSelector } from './BackgroundsSelector';
 export const MapAdditionalOptions = (): JSX.Element => {
   const dispatch = useAppDispatch();
   const defaultOverviewImageId = useSelector(projectDefaultOverviewImageIdSelector);
+  const overviewImages = useSelector(projectOverviewImagesSelector);
+  const overviewImagesEmpty = overviewImages.length === ZERO;
 
   const handleBrowseOverviewImagesClick = (): void => {
     dispatch(openOverviewImagesModalById(defaultOverviewImageId));
@@ -18,7 +24,11 @@ export const MapAdditionalOptions = (): JSX.Element => {
 
   return (
     <div className={twMerge('absolute right-6 top-[calc(64px+40px+24px)] z-10 flex')}>
-      <Button className="mr-4" onClick={handleBrowseOverviewImagesClick}>
+      <Button
+        className="mr-4"
+        onClick={handleBrowseOverviewImagesClick}
+        disabled={overviewImagesEmpty}
+      >
         Browse overview images
       </Button>
       <BackgroundSelector />
diff --git a/src/constants/index.ts b/src/constants/index.ts
index 70fa367a..84426822 100644
--- a/src/constants/index.ts
+++ b/src/constants/index.ts
@@ -1,9 +1,12 @@
 /* eslint-disable no-magic-numbers */
 
-export const BASE_API_URL = process.env.NEXT_PUBLIC_BASE_API_URL || '';
-export const BASE_MAP_IMAGES_URL = process.env.BASE_MAP_IMAGES_URL || '';
-export const BASE_NEW_API_URL = process.env.NEXT_PUBLIC_BASE_NEW_API_URL || '';
-export const PROJECT_ID = process.env.NEXT_PUBLIC_PROJECT_ID || '';
+import { getConfigValue, getProjectIdFromUrl } from './index.utils';
+
+export const BASE_API_URL = getConfigValue('BASE_API_URL');
+export const BASE_MAP_IMAGES_URL = getConfigValue('BASE_MAP_IMAGES_URL');
+export const BASE_NEW_API_URL = getConfigValue('BASE_NEW_API_URL');
+export const DEFAULT_PROJECT_ID = getConfigValue('DEFAULT_PROJECT_ID');
+export const PROJECT_ID = getProjectIdFromUrl() || DEFAULT_PROJECT_ID;
 export const ZOD_SEED = parseInt(process.env.ZOD_SEED || '123', 10);
 export const BIO_ENTITY = 'bioEntity';
 export const DRUGS_CHEMICALS = ['drugs', 'chemicals'];
diff --git a/src/constants/index.utils.test.ts b/src/constants/index.utils.test.ts
new file mode 100644
index 00000000..2d9ddfd0
--- /dev/null
+++ b/src/constants/index.utils.test.ts
@@ -0,0 +1,33 @@
+import { getConfigValue, getProjectIdFromUrl } from './index.utils';
+
+describe('getConfigValue - util', () => {
+  it('should return value for existing key', () => {
+    expect(getConfigValue('BASE_API_URL')).toBe('https://lux1.atcomp.pl/minerva/api');
+  });
+
+  it('should return default value for non-existing key', () => {
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    expect(getConfigValue('nonExistingKey' as any)).toBe('');
+  });
+});
+
+describe('getProjectIdFromUrl - util', () => {
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+  it('should return project ID from URL if present', () => {
+    jest.spyOn(URLSearchParams.prototype, 'get').mockReturnValue('my_project_id');
+    expect(getProjectIdFromUrl()).toBe('my_project_id');
+  });
+
+  it('should return default project ID if not present in URL', () => {
+    jest.spyOn(URLSearchParams.prototype, 'get').mockReturnValue(null);
+    expect(getProjectIdFromUrl()).toBeNull();
+  });
+  it('should return undefined if window is undefined', () => {
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    jest.spyOn(global as any, 'window', 'get').mockImplementation(() => undefined);
+
+    expect(getProjectIdFromUrl()).toBeUndefined();
+  });
+});
diff --git a/src/constants/index.utils.ts b/src/constants/index.utils.ts
new file mode 100644
index 00000000..ebd2553b
--- /dev/null
+++ b/src/constants/index.utils.ts
@@ -0,0 +1,19 @@
+type ConfigKeys = keyof typeof window.config;
+
+export const getConfigValue = (key: ConfigKeys): string => {
+  const defaultValue = '';
+  if (typeof window !== 'undefined' && window.config && window.config[key]) {
+    return window.config[key];
+  }
+  return defaultValue;
+};
+
+export const getProjectIdFromUrl = (): string | null | undefined => {
+  if (typeof window !== 'undefined') {
+    const params = new URLSearchParams(window.location.search);
+
+    return params.get('id');
+  }
+
+  return undefined;
+};
diff --git a/src/models/projectSchema.ts b/src/models/projectSchema.ts
index 517d7b0c..611b93a7 100644
--- a/src/models/projectSchema.ts
+++ b/src/models/projectSchema.ts
@@ -23,5 +23,5 @@ export const projectSchema = z.object({
   creationDate: z.string(),
   mapCanvasType: z.string(),
   overviewImageViews: z.array(overviewImageView),
-  topOverviewImage: overviewImageView,
+  topOverviewImage: overviewImageView.nullable(),
 });
diff --git a/src/redux/overlays/overlays.thunks.ts b/src/redux/overlays/overlays.thunks.ts
index 4b1fa460..67fd7b7d 100644
--- a/src/redux/overlays/overlays.thunks.ts
+++ b/src/redux/overlays/overlays.thunks.ts
@@ -14,6 +14,7 @@ import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
 import { showToast } from '@/utils/showToast';
 import { getErrorMessage } from '@/utils/getErrorMessage';
 import { ThunkConfig } from '@/types/store';
+import { BASE_API_URL } from '@/constants';
 import { apiPath } from '../apiPath';
 import {
   CHUNK_SIZE,
@@ -124,7 +125,7 @@ const uploadContent = async ({ createdFile, overlayContent }: UploadContentArgs)
     const chunk = data.slice(uploadedLength, uploadedLength + CHUNK_SIZE);
 
     const responeJSON = await fetch(
-      `${process.env.NEXT_PUBLIC_BASE_API_URL}/${apiPath.uploadOverlayFileContent(createdFile.id)}`,
+      `${BASE_API_URL}/${apiPath.uploadOverlayFileContent(createdFile.id)}`,
       {
         method: 'POST',
         credentials: 'include',
diff --git a/src/redux/project/project.mock.ts b/src/redux/project/project.mock.ts
index dada67e4..0b8da5ef 100644
--- a/src/redux/project/project.mock.ts
+++ b/src/redux/project/project.mock.ts
@@ -6,9 +6,10 @@ export const PROJECT_STATE_INITIAL_MOCK: ProjectState = {
   data: undefined,
   loading: 'idle',
   error: DEFAULT_ERROR,
+  projectId: '',
 };
 
-export const PROJECT_OVERVIEW_IMAGE_MOCK: OverviewImageView = {
+export const PROJECT_OVERVIEW_IMAGE_MOCK: NonNullable<OverviewImageView> = {
   idObject: 440,
   filename: '9d4911bdeeea752f076e57a91d9b1f45/biolayout_main_root.png',
   width: 5776,
diff --git a/src/redux/project/project.reducers.test.ts b/src/redux/project/project.reducers.test.ts
index f3c00776..0521d9c2 100644
--- a/src/redux/project/project.reducers.test.ts
+++ b/src/redux/project/project.reducers.test.ts
@@ -17,6 +17,7 @@ const INITIAL_STATE: ProjectState = {
   data: undefined,
   loading: 'idle',
   error: { name: '', message: '' },
+  projectId: '',
 };
 
 describe('project reducer', () => {
diff --git a/src/redux/project/project.reducers.ts b/src/redux/project/project.reducers.ts
index 435d55e8..e022b8f8 100644
--- a/src/redux/project/project.reducers.ts
+++ b/src/redux/project/project.reducers.ts
@@ -1,4 +1,4 @@
-import { getProjectById } from '@/redux/project/project.thunks';
+import { getProjectById, setProjectId } from '@/redux/project/project.thunks';
 import { ProjectState } from '@/redux/project/project.types';
 import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
 
@@ -15,3 +15,9 @@ export const getProjectByIdReducer = (builder: ActionReducerMapBuilder<ProjectSt
     // TODO to discuss manage state of failure
   });
 };
+
+export const setProjectIdReducer = (builder: ActionReducerMapBuilder<ProjectState>): void => {
+  builder.addCase(setProjectId.fulfilled, (state, action) => {
+    state.projectId = action.payload;
+  });
+};
diff --git a/src/redux/project/project.selectors.ts b/src/redux/project/project.selectors.ts
index 6ae2769e..aa0c62ae 100644
--- a/src/redux/project/project.selectors.ts
+++ b/src/redux/project/project.selectors.ts
@@ -10,7 +10,7 @@ export const projectDataSelector = createSelector(projectSelector, project => pr
 
 export const projectDefaultOverviewImageIdSelector = createSelector(
   projectDataSelector,
-  projectData => projectData?.topOverviewImage.idObject || OVERVIEW_IMAGE_ID_DEFAULT,
+  projectData => projectData?.topOverviewImage?.idObject || OVERVIEW_IMAGE_ID_DEFAULT,
 );
 
 export const currentOverviewImageSelector = createSelector(
@@ -63,3 +63,5 @@ export const organismNameSelector = createSelector(
 );
 
 export const versionSelector = createSelector(projectDataSelector, state => state?.version);
+
+export const projectMainIdSelector = createSelector(projectSelector, project => project.projectId);
diff --git a/src/redux/project/project.slice.ts b/src/redux/project/project.slice.ts
index a6e0d9d0..6fb716ec 100644
--- a/src/redux/project/project.slice.ts
+++ b/src/redux/project/project.slice.ts
@@ -1,11 +1,12 @@
 import { ProjectState } from '@/redux/project/project.types';
 import { createSlice } from '@reduxjs/toolkit';
-import { getProjectByIdReducer } from './project.reducers';
+import { getProjectByIdReducer, setProjectIdReducer } from './project.reducers';
 
 const initialState: ProjectState = {
   data: undefined,
   loading: 'idle',
   error: { name: '', message: '' },
+  projectId: '',
 };
 
 const projectSlice = createSlice({
@@ -14,6 +15,7 @@ const projectSlice = createSlice({
   reducers: {},
   extraReducers: builder => {
     getProjectByIdReducer(builder);
+    setProjectIdReducer(builder);
   },
 });
 
diff --git a/src/redux/project/project.thunks.ts b/src/redux/project/project.thunks.ts
index 2ef8de82..23a73a09 100644
--- a/src/redux/project/project.thunks.ts
+++ b/src/redux/project/project.thunks.ts
@@ -5,8 +5,10 @@ import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
 import { createAsyncThunk } from '@reduxjs/toolkit';
 import { getErrorMessage } from '@/utils/getErrorMessage';
 import { ThunkConfig } from '@/types/store';
+import { DEFAULT_PROJECT_ID } from '@/constants';
 import { apiPath } from '../apiPath';
 import { PROJECT_FETCHING_ERROR_PREFIX } from './project.constants';
+import { SetProjectIdParams } from './project.types';
 
 export const getProjectById = createAsyncThunk<Project | undefined, string, ThunkConfig>(
   'project/getProjectById',
@@ -23,3 +25,12 @@ export const getProjectById = createAsyncThunk<Project | undefined, string, Thun
     }
   },
 );
+
+export const setProjectId = createAsyncThunk<string, SetProjectIdParams, ThunkConfig>(
+  'project/setProjectId',
+  async ({ queryData }) => {
+    const projectId = queryData?.id || DEFAULT_PROJECT_ID;
+
+    return projectId;
+  },
+);
diff --git a/src/redux/project/project.types.ts b/src/redux/project/project.types.ts
index c92be2c4..df7dad10 100644
--- a/src/redux/project/project.types.ts
+++ b/src/redux/project/project.types.ts
@@ -1,8 +1,12 @@
 import { Loading } from '@/types/loadingState';
 import { Project } from '@/types/models';
+import { QueryData } from '@/types/query';
 
 export type ProjectState = {
+  projectId: string;
   data: Project | undefined;
   loading: Loading;
   error: Error;
 };
+
+export type SetProjectIdParams = { queryData: QueryData };
diff --git a/src/redux/root/init.thunks.ts b/src/redux/root/init.thunks.ts
index fc1c0f25..23aa9368 100644
--- a/src/redux/root/init.thunks.ts
+++ b/src/redux/root/init.thunks.ts
@@ -20,7 +20,7 @@ import {
   getAllUserOverlaysByCreator,
 } from '../overlays/overlays.thunks';
 import { getAllPlugins, getInitPlugins } from '../plugins/plugins.thunks';
-import { getProjectById } from '../project/project.thunks';
+import { getProjectById, setProjectId } from '../project/project.thunks';
 import { setPerfectMatch } from '../search/search.slice';
 import { getSearchData } from '../search/search.thunks';
 import { getStatisticsById } from '../statistics/statistics.thunks';
@@ -35,6 +35,8 @@ export const fetchInitialAppData = createAsyncThunk<
   InitializeAppParams,
   { dispatch: AppDispatch }
 >('appInit/fetchInitialAppData', async ({ queryData }, { dispatch }): Promise<void> => {
+  dispatch(setProjectId({ queryData }));
+
   if (queryData.pluginsId) {
     await dispatch(
       getInitPlugins({
diff --git a/src/redux/root/query.selectors.ts b/src/redux/root/query.selectors.ts
index b862ed42..8c4f1038 100644
--- a/src/redux/root/query.selectors.ts
+++ b/src/redux/root/query.selectors.ts
@@ -5,6 +5,7 @@ import { mapDataSelector } from '../map/map.selectors';
 import { perfectMatchSelector, searchValueSelector } from '../search/search.selectors';
 import { activeOverlaysIdSelector } from '../overlayBioEntity/overlayBioEntity.selector';
 import { activePluginsIdSelector } from '../plugins/plugins.selectors';
+import { projectMainIdSelector } from '../project/project.selectors';
 
 export const queryDataParamsSelector = createSelector(
   searchValueSelector,
@@ -12,12 +13,14 @@ export const queryDataParamsSelector = createSelector(
   mapDataSelector,
   activeOverlaysIdSelector,
   activePluginsIdSelector,
+  projectMainIdSelector,
   (
     searchValue,
     perfectMatch,
     { modelId, backgroundId, position },
     activeOverlaysId,
     activePluginsId,
+    id,
   ): QueryDataParams => {
     const joinedSearchValue = searchValue.join(';');
     const shouldIncludeSearchValue = searchValue.length > ZERO && joinedSearchValue;
@@ -26,6 +29,7 @@ export const queryDataParamsSelector = createSelector(
     const shouldIncludePluginsId = activePluginsId.length > ZERO;
 
     const queryDataParams: QueryDataParams = {
+      id,
       perfectMatch,
       modelId,
       backgroundId,
diff --git a/src/types/query.ts b/src/types/query.ts
index be3453f0..e41e2520 100644
--- a/src/types/query.ts
+++ b/src/types/query.ts
@@ -1,6 +1,7 @@
 import { Point } from './map';
 
 export interface QueryData {
+  id?: string;
   searchValue?: string[];
   perfectMatch: boolean;
   modelId?: number;
@@ -11,6 +12,7 @@ export interface QueryData {
 }
 
 export interface QueryDataParams {
+  id?: string;
   searchValue?: string;
   perfectMatch: boolean;
   modelId?: number;
@@ -23,6 +25,7 @@ export interface QueryDataParams {
 }
 
 export interface QueryDataRouterParams {
+  id?: string;
   searchValue?: string;
   perfectMatch?: string;
   modelId?: string;
diff --git a/src/utils/parseQueryToTypes.ts b/src/utils/parseQueryToTypes.ts
index f04abadf..5aebfe4a 100644
--- a/src/utils/parseQueryToTypes.ts
+++ b/src/utils/parseQueryToTypes.ts
@@ -1,6 +1,7 @@
 import { QueryData, QueryDataRouterParams } from '@/types/query';
 
 export const parseQueryToTypes = (query: QueryDataRouterParams): QueryData => ({
+  id: query.id,
   searchValue: query.searchValue?.split(';'),
   perfectMatch: query?.perfectMatch === 'true' || false,
   modelId: Number(query.modelId) || undefined,
diff --git a/src/utils/query-manager/useReduxBusQueryManager.test.ts b/src/utils/query-manager/useReduxBusQueryManager.test.ts
index c77f8f3f..adecf259 100644
--- a/src/utils/query-manager/useReduxBusQueryManager.test.ts
+++ b/src/utils/query-manager/useReduxBusQueryManager.test.ts
@@ -48,6 +48,7 @@ describe('useReduxBusQueryManager - util', () => {
       project: {
         ...loadedDataMock,
         data: undefined,
+        projectId: '',
       },
       map: {
         ...loadedDataMock,
-- 
GitLab