From 3c10830e879e467a5a9a8db174756012f4311766 Mon Sep 17 00:00:00 2001
From: mateusz-winiarczyk <mateusz.winiarczyk@appunite.com>
Date: Tue, 5 Mar 2024 10:04:25 +0100
Subject: [PATCH] fix(overlays): displaying users overlays (MIN-280)

---
 .../UserOverlayForm.component.test.tsx        |  66 +++++++++-
 .../hooks/useUserOverlayForm.ts               |   4 +-
 .../overlaysLayer/useOverlayFeatures.test.ts  | 106 +++++++++++++++-
 .../overlayBioEntity.selector.ts              |  41 ++++--
 .../overlayBioEntity.types.ts                 |   5 +
 .../overlayBioEntity.utils.test.ts            | 118 +++++++++++++++++-
 .../overlayBioEntity.utils.ts                 |  52 ++++++++
 src/redux/overlays/overlays.mock.ts           |  29 +++++
 src/redux/overlays/overlays.selectors.ts      |   5 +
 src/redux/overlays/overlays.thunks.ts         |  72 ++++++-----
 10 files changed, 446 insertions(+), 52 deletions(-)

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 9fb75c28..474934d8 100644
--- a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.component.test.tsx
+++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.component.test.tsx
@@ -1,6 +1,6 @@
 /* eslint-disable no-magic-numbers */
 import React from 'react';
-import { render, screen, fireEvent } from '@testing-library/react';
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
 import { projectFixture } from '@/models/fixtures/projectFixture';
 import {
   InitialStoreState,
@@ -16,9 +16,11 @@ import { apiPath } from '@/redux/apiPath';
 import {
   createdOverlayFileFixture,
   createdOverlayFixture,
+  overlaysFixture,
   uploadedOverlayFileContentFixture,
 } from '@/models/fixtures/overlaysFixture';
 import { OVERLAYS_INITIAL_STATE_MOCK } from '@/redux/overlays/overlays.mock';
+import { DEFAULT_ERROR } from '@/constants/errors';
 import { UserOverlayForm } from './UserOverlayForm.component';
 
 const mockedAxiosClient = mockNetworkResponse();
@@ -68,11 +70,11 @@ describe('UserOverlayForm - Component', () => {
       .reply(HttpStatusCode.Ok, createdOverlayFileFixture);
 
     mockedAxiosClient
-      .onPost(apiPath.uploadOverlayFileContent(123))
+      .onPost(apiPath.uploadOverlayFileContent(createdOverlayFileFixture.id))
       .reply(HttpStatusCode.Ok, uploadedOverlayFileContentFixture);
 
     mockedAxiosClient
-      .onPost(apiPath.createOverlay('pd'))
+      .onPost(apiPath.createOverlay(projectFixture.projectId))
       .reply(HttpStatusCode.Ok, createdOverlayFixture);
 
     renderComponent({
@@ -189,4 +191,62 @@ describe('UserOverlayForm - Component', () => {
 
     expect(currentStep).toBe(1);
   });
+  it('should refetch user overlays after submit the form', async () => {
+    mockedAxiosClient
+      .onPost(apiPath.createOverlayFile())
+      .reply(HttpStatusCode.Ok, createdOverlayFileFixture);
+
+    mockedAxiosClient
+      .onPost(apiPath.uploadOverlayFileContent(createdOverlayFileFixture.id))
+      .reply(HttpStatusCode.Ok, uploadedOverlayFileContentFixture);
+
+    mockedAxiosClient
+      .onPost(apiPath.createOverlay(projectFixture.projectId))
+      .reply(HttpStatusCode.Ok, createdOverlayFixture);
+
+    mockedAxiosClient
+      .onGet(apiPath.getAllUserOverlaysByCreatorQuery({ creator: 'test', publicOverlay: false }))
+      .reply(HttpStatusCode.Ok, overlaysFixture);
+
+    const { store } = renderComponent({
+      user: {
+        authenticated: true,
+        error: DEFAULT_ERROR,
+        login: 'test',
+        loading: 'succeeded',
+      },
+      project: {
+        data: projectFixture,
+        loading: 'succeeded',
+        error: DEFAULT_ERROR,
+      },
+      overlays: OVERLAYS_INITIAL_STATE_MOCK,
+    });
+
+    const userOverlays = store.getState().overlays.userOverlays.data;
+
+    expect(userOverlays).toEqual([]);
+
+    fireEvent.change(screen.getByTestId('overlay-name'), { target: { value: 'Test Overlay' } });
+    fireEvent.change(screen.getByTestId('overlay-description'), {
+      target: { value: 'Description Overlay' },
+    });
+    fireEvent.change(screen.getByTestId('overlay-elements-list'), {
+      target: { value: 'Elements List Overlay' },
+    });
+
+    fireEvent.click(screen.getByLabelText('upload overlay'));
+
+    expect(screen.getByLabelText('upload overlay')).toBeDisabled();
+
+    await waitFor(() => {
+      expect(store.getState().overlays.userOverlays.loading).toBe('succeeded');
+    });
+
+    const refetchedUserOverlays = store.getState().overlays.userOverlays.data;
+
+    await waitFor(() => {
+      expect(refetchedUserOverlays).toEqual(overlaysFixture);
+    });
+  });
 });
diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/hooks/useUserOverlayForm.ts b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/hooks/useUserOverlayForm.ts
index dda281bf..5229c9fa 100644
--- a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/hooks/useUserOverlayForm.ts
+++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/hooks/useUserOverlayForm.ts
@@ -100,12 +100,12 @@ export const useUserOverlayForm = (): ReturnType => {
       filename = 'unknown.txt'; // Elements list is sent to the backend as a file, so we need to create a filename for the elements list.
     }
 
-    if (!overlayContent || !projectId || !description || !name) return;
+    if (!overlayContent || !projectId || !name) return;
 
     dispatch(
       addOverlay({
         content: overlayContent,
-        description,
+        description: description || '',
         filename,
         name,
         projectId,
diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.test.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.test.ts
index 3c4dbcfd..74d8d6bf 100644
--- a/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.test.ts
+++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.test.ts
@@ -4,10 +4,14 @@ import { mapStateWithCurrentlySelectedMainMapFixture } from '@/redux/map/map.fix
 import { SURFACE_MARKER } from '@/redux/models/marker.mock';
 import { MODELS_DATA_MOCK_WITH_MAIN_MAP } from '@/redux/models/models.mock';
 import { MOCKED_OVERLAY_BIO_ENTITY_RENDER } from '@/redux/overlayBioEntity/overlayBioEntity.mock';
-import { OVERLAYS_PUBLIC_FETCHED_STATE_MOCK } from '@/redux/overlays/overlays.mock';
+import {
+  OVERLAYS_PUBLIC_FETCHED_STATE_MOCK,
+  USER_OVERLAYS_MOCK,
+} from '@/redux/overlays/overlays.mock';
 import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
 import { renderHook } from '@testing-library/react';
 import { useOverlayFeatures } from './useOverlayFeatures';
+import * as getPolygonLatitudeCoordinates from './getPolygonLatitudeCoordinates';
 
 /**
  * mocks for useOverlayFeatures
@@ -15,7 +19,20 @@ import { useOverlayFeatures } from './useOverlayFeatures';
  * point of the test is to test if all helper functions work correctly when combined together
  */
 
+jest.mock('./getPolygonLatitudeCoordinates', () => ({
+  __esModule: true,
+  ...jest.requireActual('./getPolygonLatitudeCoordinates'),
+}));
+
+const getPolygonLatitudeCoordinatesSpy = jest.spyOn(
+  getPolygonLatitudeCoordinates,
+  'getPolygonLatitudeCoordinates',
+);
+
 describe('useOverlayFeatures', () => {
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
   const { Wrapper } = getReduxWrapperWithStore({
     configuration: {
       ...CONFIGURATION_INITIAL_STORE_MOCKS,
@@ -100,4 +117,91 @@ describe('useOverlayFeatures', () => {
     // @ts-ignore
     expect(features[10].getStyle().getFill().getColor()).toBe('#0000001a');
   });
+
+  it('should get coordinates only for active general overlays and user overlays if exist', () => {
+    const { Wrapper: OverlayFeaturesWrapper } = getReduxWrapperWithStore({
+      configuration: {
+        ...CONFIGURATION_INITIAL_STORE_MOCKS,
+      },
+      overlayBioEntity: {
+        overlaysId: [11, 12, 99, 123],
+        data: {
+          // overlayId
+          11: {
+            // modelId
+            52: MOCKED_OVERLAY_BIO_ENTITY_RENDER,
+            53: MOCKED_OVERLAY_BIO_ENTITY_RENDER,
+          },
+          12: {
+            52: MOCKED_OVERLAY_BIO_ENTITY_RENDER,
+            53: MOCKED_OVERLAY_BIO_ENTITY_RENDER,
+          },
+          99: {
+            52: MOCKED_OVERLAY_BIO_ENTITY_RENDER,
+            53: MOCKED_OVERLAY_BIO_ENTITY_RENDER,
+          },
+          123: {
+            52: MOCKED_OVERLAY_BIO_ENTITY_RENDER,
+            53: MOCKED_OVERLAY_BIO_ENTITY_RENDER,
+          },
+        },
+      },
+      overlays: {
+        ...OVERLAYS_PUBLIC_FETCHED_STATE_MOCK,
+        userOverlays: {
+          data: USER_OVERLAYS_MOCK,
+          error: { message: '', name: '' },
+          loading: 'succeeded',
+        },
+      },
+      map: {
+        ...mapStateWithCurrentlySelectedMainMapFixture,
+      },
+      models: {
+        ...MODELS_DATA_MOCK_WITH_MAIN_MAP,
+      },
+    });
+
+    renderHook(() => useOverlayFeatures(), {
+      wrapper: OverlayFeaturesWrapper,
+    });
+
+    expect(getPolygonLatitudeCoordinatesSpy).toHaveBeenLastCalledWith({
+      nOverlays: 4,
+      overlayIndexBasedOnOrder: 0,
+      width: 8.923194537814197,
+      xMin: 4454.850442288663,
+    });
+  });
+  it('should not get coordinates for active overlays if no overlay is active', () => {
+    const { Wrapper: OverlayFeaturesWrapper } = getReduxWrapperWithStore({
+      configuration: {
+        ...CONFIGURATION_INITIAL_STORE_MOCKS,
+      },
+      overlayBioEntity: {
+        overlaysId: [],
+        data: {},
+      },
+      overlays: {
+        ...OVERLAYS_PUBLIC_FETCHED_STATE_MOCK,
+        userOverlays: {
+          data: USER_OVERLAYS_MOCK,
+          error: { message: '', name: '' },
+          loading: 'succeeded',
+        },
+      },
+      map: {
+        ...mapStateWithCurrentlySelectedMainMapFixture,
+      },
+      models: {
+        ...MODELS_DATA_MOCK_WITH_MAIN_MAP,
+      },
+    });
+
+    renderHook(() => useOverlayFeatures(), {
+      wrapper: OverlayFeaturesWrapper,
+    });
+
+    expect(getPolygonLatitudeCoordinatesSpy).not.toHaveBeenCalled();
+  });
 });
diff --git a/src/redux/overlayBioEntity/overlayBioEntity.selector.ts b/src/redux/overlayBioEntity/overlayBioEntity.selector.ts
index e42ee1cb..f6ad669d 100644
--- a/src/redux/overlayBioEntity/overlayBioEntity.selector.ts
+++ b/src/redux/overlayBioEntity/overlayBioEntity.selector.ts
@@ -2,9 +2,18 @@ import { OverlayBioEntityRender } from '@/types/OLrendering';
 import { createSelector } from '@reduxjs/toolkit';
 import { currentSearchedBioEntityId } from '../drawer/drawer.selectors';
 import { currentModelIdSelector } from '../models/models.selectors';
-import { overlaysDataSelector, overlaysIdsAndOrderSelector } from '../overlays/overlays.selectors';
+import {
+  overlaysDataSelector,
+  overlaysIdsAndOrderSelector,
+  userOverlaysDataSelector,
+  userOverlaysIdsAndOrderSelector,
+} from '../overlays/overlays.selectors';
 import { rootSelector } from '../root/root.selectors';
-import { calculateOvarlaysOrder } from './overlayBioEntity.utils';
+import {
+  calculateOvarlaysOrder,
+  getActiveOverlaysIdsAndOrder,
+  getActiveUserOverlaysIdsAndOrder,
+} from './overlayBioEntity.utils';
 
 export const overlayBioEntitySelector = createSelector(
   rootSelector,
@@ -52,19 +61,35 @@ export const isOverlayLoadingSelector = createSelector(
 export const activeOverlaysSelector = createSelector(
   rootSelector,
   overlaysDataSelector,
-  (state, overlaysData) =>
-    overlaysData.filter(overlay => isOverlayActiveSelector(state, overlay.idObject)),
+  userOverlaysDataSelector,
+  (state, overlaysData, userOverlaysData) => {
+    const activeOverlays = overlaysData.filter(overlay =>
+      isOverlayActiveSelector(state, overlay.idObject),
+    );
+    const activeUserOverlays =
+      userOverlaysData?.filter(overlay => isOverlayActiveSelector(state, overlay.idObject)) || [];
+
+    return [...activeOverlays, ...activeUserOverlays];
+  },
 );
 
 export const getOverlayOrderSelector = createSelector(
   overlaysIdsAndOrderSelector,
+  userOverlaysIdsAndOrderSelector,
   activeOverlaysIdSelector,
-  (overlaysIdsAndOrder, activeOverlaysIds) => {
-    const activeOverlaysIdsAndOrder = overlaysIdsAndOrder.filter(({ idObject }) =>
-      activeOverlaysIds.includes(idObject),
+  (overlaysIdsAndOrder, userOverlaysIdsAndOrder, activeOverlaysIds) => {
+    const { activeOverlaysIdsAndOrder, maxOrderValue } = getActiveOverlaysIdsAndOrder(
+      overlaysIdsAndOrder,
+      activeOverlaysIds,
+    );
+
+    const activeUserOverlaysIdsAndOrder = getActiveUserOverlaysIdsAndOrder(
+      userOverlaysIdsAndOrder,
+      activeOverlaysIds,
+      maxOrderValue,
     );
 
-    return calculateOvarlaysOrder(activeOverlaysIdsAndOrder);
+    return calculateOvarlaysOrder([...activeOverlaysIdsAndOrder, ...activeUserOverlaysIdsAndOrder]);
   },
 );
 
diff --git a/src/redux/overlayBioEntity/overlayBioEntity.types.ts b/src/redux/overlayBioEntity/overlayBioEntity.types.ts
index 4074058b..2733514a 100644
--- a/src/redux/overlayBioEntity/overlayBioEntity.types.ts
+++ b/src/redux/overlayBioEntity/overlayBioEntity.types.ts
@@ -13,3 +13,8 @@ export type OverlaysBioEntityState = {
 export type RemoveOverlayBioEntityForGivenOverlayPayload = { overlayId: number };
 export type RemoveOverlayBioEntityForGivenOverlayAction =
   PayloadAction<RemoveOverlayBioEntityForGivenOverlayPayload>;
+
+export type OverlaysIdsAndOrder = {
+  idObject: number;
+  order: number;
+}[];
diff --git a/src/redux/overlayBioEntity/overlayBioEntity.utils.test.ts b/src/redux/overlayBioEntity/overlayBioEntity.utils.test.ts
index abb5b8fb..d4248b65 100644
--- a/src/redux/overlayBioEntity/overlayBioEntity.utils.test.ts
+++ b/src/redux/overlayBioEntity/overlayBioEntity.utils.test.ts
@@ -1,6 +1,12 @@
+/* eslint-disable no-magic-numbers */
 import { overlayBioEntityFixture } from '@/models/fixtures/overlayBioEntityFixture';
 import { OverlayBioEntity } from '@/types/models';
-import { calculateOvarlaysOrder, getValidOverlayBioEntities } from './overlayBioEntity.utils';
+import {
+  calculateOvarlaysOrder,
+  getActiveOverlaysIdsAndOrder,
+  getActiveUserOverlaysIdsAndOrder,
+  getValidOverlayBioEntities,
+} from './overlayBioEntity.utils';
 
 describe('calculateOverlaysOrder', () => {
   const cases = [
@@ -98,3 +104,113 @@ describe('getValidOverlayBioEntities', () => {
     expect(result).toEqual([]);
   });
 });
+
+describe('getActiveUserOverlaysIdsAndOrder', () => {
+  const userOverlaysIdsAndOrder = [
+    { idObject: 1, order: 1 },
+    { idObject: 2, order: 2 },
+    { idObject: 3, order: 3 },
+    { idObject: 4, order: 4 },
+  ];
+
+  const activeOverlaysIds = [2, 4];
+  const maxOrderValue = 10;
+
+  it('should return active user overlays with updated order values', () => {
+    const expectedOutput = [
+      { idObject: 2, order: 12 },
+      { idObject: 4, order: 14 },
+    ];
+    expect(
+      getActiveUserOverlaysIdsAndOrder(userOverlaysIdsAndOrder, activeOverlaysIds, maxOrderValue),
+    ).toEqual(expectedOutput);
+  });
+
+  it('should return an empty array when there are no active overlays', () => {
+    expect(getActiveUserOverlaysIdsAndOrder(userOverlaysIdsAndOrder, [], maxOrderValue)).toEqual(
+      [],
+    );
+  });
+
+  it('should return all user overlays with updated order values when all overlays are active', () => {
+    const allOverlaysActive = [1, 2, 3, 4];
+    const expectedOutput = [
+      { idObject: 1, order: 11 },
+      { idObject: 2, order: 12 },
+      { idObject: 3, order: 13 },
+      { idObject: 4, order: 14 },
+    ];
+    expect(
+      getActiveUserOverlaysIdsAndOrder(userOverlaysIdsAndOrder, allOverlaysActive, maxOrderValue),
+    ).toEqual(expectedOutput);
+  });
+
+  it('should return active user overlays with order values starting from 1 when maxOrderValue is 0', () => {
+    const maxOrderZero = 0;
+    const expectedOutput = [
+      { idObject: 2, order: 2 },
+      { idObject: 4, order: 4 },
+    ];
+    expect(
+      getActiveUserOverlaysIdsAndOrder(userOverlaysIdsAndOrder, activeOverlaysIds, maxOrderZero),
+    ).toEqual(expectedOutput);
+  });
+});
+
+describe('getActiveOverlaysIdsAndOrder', () => {
+  const overlaysIdsAndOrder = [
+    { idObject: 1, order: 1 },
+    { idObject: 2, order: 2 },
+    { idObject: 3, order: 3 },
+    { idObject: 4, order: 4 },
+  ];
+
+  const activeOverlaysIds = [2, 4];
+
+  it('should return maxOrderValue and active overlays', () => {
+    const expectedOutput = {
+      maxOrderValue: 4,
+      activeOverlaysIdsAndOrder: [
+        { idObject: 2, order: 2 },
+        { idObject: 4, order: 4 },
+      ],
+    };
+    expect(getActiveOverlaysIdsAndOrder(overlaysIdsAndOrder, activeOverlaysIds)).toEqual(
+      expectedOutput,
+    );
+  });
+
+  it('should return maxOrderValue as 0 when there are no active overlays', () => {
+    const expectedOutput = {
+      maxOrderValue: 0,
+      activeOverlaysIdsAndOrder: [],
+    };
+    expect(getActiveOverlaysIdsAndOrder(overlaysIdsAndOrder, [])).toEqual(expectedOutput);
+  });
+
+  it('should return maxOrderValue and all overlays when all overlays are active', () => {
+    const allOverlaysActive = [1, 2, 3, 4];
+    const expectedOutput = {
+      maxOrderValue: 4,
+      activeOverlaysIdsAndOrder: overlaysIdsAndOrder,
+    };
+    expect(getActiveOverlaysIdsAndOrder(overlaysIdsAndOrder, allOverlaysActive)).toEqual(
+      expectedOutput,
+    );
+  });
+
+  it('should return maxOrderValue as 0 and no active overlays when none of the overlays are active', () => {
+    const expectedOutput = {
+      maxOrderValue: 0,
+      activeOverlaysIdsAndOrder: [],
+    };
+    expect(getActiveOverlaysIdsAndOrder(overlaysIdsAndOrder, [])).toEqual(expectedOutput);
+  });
+  it('should return maxOrderValue as 0 and no active overlays when there are no overlays', () => {
+    const expectedOutput = {
+      maxOrderValue: 0,
+      activeOverlaysIdsAndOrder: [],
+    };
+    expect(getActiveOverlaysIdsAndOrder([], [])).toEqual(expectedOutput);
+  });
+});
diff --git a/src/redux/overlayBioEntity/overlayBioEntity.utils.ts b/src/redux/overlayBioEntity/overlayBioEntity.utils.ts
index becf2d2a..420ec1d0 100644
--- a/src/redux/overlayBioEntity/overlayBioEntity.utils.ts
+++ b/src/redux/overlayBioEntity/overlayBioEntity.utils.ts
@@ -1,3 +1,4 @@
+/* eslint-disable no-magic-numbers */
 import { ONE } from '@/constants/common';
 import { overlayBioEntitySchema } from '@/models/overlayBioEntitySchema';
 import { OverlayBioEntityRender } from '@/types/OLrendering';
@@ -5,6 +6,7 @@ import { OverlayBioEntity } from '@/types/models';
 import { getOverlayReactionCoordsFromLine } from '@/utils/overlays/getOverlayReactionCoords';
 import { isBioEntity, isReaction, isSubmapLink } from '@/utils/overlays/overlaysElementsTypeGuards';
 import { z } from 'zod';
+import { OverlaysIdsAndOrder } from './overlayBioEntity.types';
 
 export const parseOverlayBioEntityToOlRenderingFormat = (
   data: OverlayBioEntity[],
@@ -142,3 +144,53 @@ export const getValidOverlayBioEntities = (
 
   return parsedOverlayBioEntities.success ? parsedOverlayBioEntities.data : undefined;
 };
+
+type GetActiveOverlaysIdsAndOrderReturnType = {
+  maxOrderValue: number;
+  activeOverlaysIdsAndOrder: OverlaysIdsAndOrder;
+};
+
+export const getActiveOverlaysIdsAndOrder = (
+  overlaysIdsAndOrder: OverlaysIdsAndOrder,
+  activeOverlaysIds: number[],
+): GetActiveOverlaysIdsAndOrderReturnType => {
+  let maxOrderValue = -Infinity;
+  const activeOverlaysIdsAndOrder = overlaysIdsAndOrder.filter(({ idObject }) =>
+    activeOverlaysIds.includes(idObject),
+  );
+
+  overlaysIdsAndOrder.forEach(({ idObject, order }) => {
+    const isActive = activeOverlaysIds.includes(idObject);
+    if (isActive && order > maxOrderValue) maxOrderValue = order;
+  });
+
+  if (maxOrderValue === -Infinity) {
+    maxOrderValue = 0;
+  }
+
+  return {
+    maxOrderValue,
+    activeOverlaysIdsAndOrder,
+  };
+};
+
+export const getActiveUserOverlaysIdsAndOrder = (
+  userOverlaysIdsAndOrder: OverlaysIdsAndOrder,
+  activeOverlaysIds: number[],
+  maxOrderValue: number,
+): OverlaysIdsAndOrder => {
+  const clonedUserOverlaysIdsAndOrder: OverlaysIdsAndOrder = JSON.parse(
+    JSON.stringify(userOverlaysIdsAndOrder),
+  );
+
+  const activeUserOverlaysIdsAndOrder = clonedUserOverlaysIdsAndOrder.filter(userOverlay => {
+    const isActive = activeOverlaysIds.includes(userOverlay.idObject);
+    if (isActive) {
+      /* eslint-disable-next-line no-param-reassign */
+      userOverlay.order = maxOrderValue + userOverlay.order; // user overlays appear after general overlays, we need to get max order value of general overlays and add to it user overlay order to be ensured that user overlay will appear after general overlays
+    }
+    return isActive;
+  });
+
+  return activeUserOverlaysIdsAndOrder;
+};
diff --git a/src/redux/overlays/overlays.mock.ts b/src/redux/overlays/overlays.mock.ts
index 7942bb04..4f7d8687 100644
--- a/src/redux/overlays/overlays.mock.ts
+++ b/src/redux/overlays/overlays.mock.ts
@@ -121,3 +121,32 @@ export const ADD_OVERLAY_MOCK = {
   projectId: 'pd',
   type: 'GENERIC',
 };
+
+export const USER_OVERLAYS_MOCK: MapOverlay[] = [
+  {
+    name: 'PD substantia nigra',
+    googleLicenseConsent: false,
+    creator: 'appu-admin',
+    description:
+      'Differential transcriptome expression from post mortem tissue. Meta-analysis from 8 published datasets, FDR = 0.05, see PMIDs 23832570 and 25447234.',
+    genomeType: null,
+    genomeVersion: null,
+    idObject: 99,
+    publicOverlay: true,
+    type: 'GENERIC',
+    order: 1,
+  },
+  {
+    name: 'Ageing brain',
+    googleLicenseConsent: false,
+    creator: 'appu-admin',
+    description:
+      'Differential transcriptome expression from post mortem tissue. Source: Allen Brain Atlas datasets, see PMID 25447234.',
+    genomeType: null,
+    genomeVersion: null,
+    idObject: 123,
+    publicOverlay: true,
+    type: 'GENERIC',
+    order: 2,
+  },
+];
diff --git a/src/redux/overlays/overlays.selectors.ts b/src/redux/overlays/overlays.selectors.ts
index 87409b8e..fc988894 100644
--- a/src/redux/overlays/overlays.selectors.ts
+++ b/src/redux/overlays/overlays.selectors.ts
@@ -34,6 +34,11 @@ export const userOverlaysDataSelector = createSelector(
   overlays => overlays.data,
 );
 
+export const userOverlaysIdsAndOrderSelector = createSelector(
+  userOverlaysDataSelector,
+  userOverlays => userOverlays?.map(({ idObject, order }) => ({ idObject, order })) || [],
+);
+
 export const userOverlaySelector = createSelector(
   [userOverlaysDataSelector, (_, userOverlayId: number): number => userOverlayId],
   (userOverlays, userOverlayId) =>
diff --git a/src/redux/overlays/overlays.thunks.ts b/src/redux/overlays/overlays.thunks.ts
index 6e290d9f..63316421 100644
--- a/src/redux/overlays/overlays.thunks.ts
+++ b/src/redux/overlays/overlays.thunks.ts
@@ -29,6 +29,36 @@ export const getAllPublicOverlaysByProjectId = createAsyncThunk(
   },
 );
 
+export const getAllUserOverlaysByCreator = createAsyncThunk(
+  'overlays/getAllUserOverlaysByCreator',
+  async (_, { getState }): Promise<MapOverlay[]> => {
+    const state = getState() as RootState;
+    const creator = state.user.login;
+    if (!creator) return [];
+
+    const response = await axiosInstance<MapOverlay[]>(
+      apiPath.getAllUserOverlaysByCreatorQuery({
+        creator,
+        publicOverlay: false,
+      }),
+      {
+        withCredentials: true,
+      },
+    );
+
+    const isDataValid = validateDataUsingZodSchema(response.data, z.array(mapOverlay));
+
+    const sortByOrder = (userOverlayA: MapOverlay, userOverlayB: MapOverlay): number => {
+      if (userOverlayA.order > userOverlayB.order) return 1;
+      return -1;
+    };
+
+    const sortedUserOverlays = response.data.sort(sortByOrder);
+
+    return isDataValid ? sortedUserOverlays : [];
+  },
+);
+
 /** UTILS */
 
 type CreateFileArgs = {
@@ -140,14 +170,10 @@ type AddOverlayArgs = {
 
 export const addOverlay = createAsyncThunk(
   'overlays/addOverlay',
-  async ({
-    filename,
-    content,
-    description,
-    name,
-    type,
-    projectId,
-  }: AddOverlayArgs): Promise<void> => {
+  async (
+    { filename, content, description, name, type, projectId }: AddOverlayArgs,
+    { dispatch },
+  ): Promise<void> => {
     const createdFile = await createFile({
       filename,
       content,
@@ -165,36 +191,8 @@ export const addOverlay = createAsyncThunk(
       type,
       projectId,
     });
-  },
-);
-
-export const getAllUserOverlaysByCreator = createAsyncThunk(
-  'overlays/getAllUserOverlaysByCreator',
-  async (_, { getState }): Promise<MapOverlay[]> => {
-    const state = getState() as RootState;
-    const creator = state.user.login;
-    if (!creator) return [];
 
-    const response = await axiosInstance<MapOverlay[]>(
-      apiPath.getAllUserOverlaysByCreatorQuery({
-        creator,
-        publicOverlay: false,
-      }),
-      {
-        withCredentials: true,
-      },
-    );
-
-    const isDataValid = validateDataUsingZodSchema(response.data, z.array(mapOverlay));
-
-    const sortByOrder = (userOverlayA: MapOverlay, userOverlayB: MapOverlay): number => {
-      if (userOverlayA.order > userOverlayB.order) return 1;
-      return -1;
-    };
-
-    const sortedUserOverlays = response.data.sort(sortByOrder);
-
-    return isDataValid ? sortedUserOverlays : [];
+    dispatch(getAllUserOverlaysByCreator());
   },
 );
 
-- 
GitLab