From 525ff421acc675e5e93bbe066aa89cf29e5362e7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Adrian=20Or=C5=82=C3=B3w?= <adrian.orlow@fishbrain.com>
Date: Wed, 25 Oct 2023 14:20:00 +0200
Subject: [PATCH] feat: move map init hook logic to redux

---
 .env                                          |   1 +
 .eslintrc.json                                |   4 +-
 package-lock.json                             | 381 ++++++++++++++++++
 package.json                                  |   1 +
 prettier.config.js                            |   1 +
 .../MapViewer/utils/config/getMapTileUrl.ts   |  15 +
 .../MapViewer/utils/config/useOlMapConfig.ts  |   8 +-
 .../Map/MapViewer/utils/init/useFirstStage.ts |  23 --
 .../Map/MapViewer/utils/init/useOlMapInit.ts  |   9 -
 .../MapViewer/utils/init/useSecondStage.ts    |  44 --
 .../Map/MapViewer/utils/init/useThirdStage.ts |  38 --
 .../Map/MapViewer/utils/useOlMap.ts           |   2 -
 src/components/SPA/MinervaSPA.component.tsx   |  30 +-
 src/redux/backgrounds/background.selectors.ts |   2 +-
 src/redux/map/map.middleware.ts               |  46 +++
 src/redux/map/map.reducers.ts                 |  24 +-
 src/redux/map/map.slice.ts                    |   5 +-
 src/redux/map/map.thunks.ts                   |  31 ++
 src/redux/map/map.types.ts                    |   9 +
 src/redux/models/models.thunks.test.ts        |  10 +-
 src/redux/project/project.selectors.ts        |   2 +-
 src/redux/root/init.selectors.ts              |  13 +
 src/redux/root/mapStages.selectors.ts         |  40 --
 src/redux/store.ts                            |   4 +-
 src/utils/getUpdatedMapData.ts                |  29 ++
 src/utils/map/useHandleMapChange.ts           |  40 --
 yarn.lock                                     | 190 ++++++++-
 27 files changed, 774 insertions(+), 228 deletions(-)
 create mode 100644 src/components/Map/MapViewer/utils/config/getMapTileUrl.ts
 delete mode 100644 src/components/Map/MapViewer/utils/init/useFirstStage.ts
 delete mode 100644 src/components/Map/MapViewer/utils/init/useOlMapInit.ts
 delete mode 100644 src/components/Map/MapViewer/utils/init/useSecondStage.ts
 delete mode 100644 src/components/Map/MapViewer/utils/init/useThirdStage.ts
 create mode 100644 src/redux/map/map.middleware.ts
 create mode 100644 src/redux/map/map.thunks.ts
 create mode 100644 src/redux/root/init.selectors.ts
 delete mode 100644 src/redux/root/mapStages.selectors.ts
 create mode 100644 src/utils/getUpdatedMapData.ts
 delete mode 100644 src/utils/map/useHandleMapChange.ts

diff --git a/.env b/.env
index 3f191e1f..470be3d7 100644
--- a/.env
+++ b/.env
@@ -1,5 +1,6 @@
 
 NEXT_PUBLIC_BASE_API_URL = 'https://corsproxy.io/?https://lux1.atcomp.pl/minerva/api'
 NEXT_PUBLIC_BASE_NEW_API_URL = 'https://corsproxy.io/?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/.eslintrc.json b/.eslintrc.json
index e60a21f0..3ed8a0da 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -10,6 +10,7 @@
     "plugin:react/recommended",
     "plugin:jsx-a11y/recommended",
     "plugin:@next/next/recommended",
+    "plugin:prettier/recommended",
     "prettier",
     "next"
   ],
@@ -87,7 +88,8 @@
         "callees": ["twMerge"],
         "config": "./tailwind.config.ts"
       }
-    ]
+    ],
+    "prettier/prettier": "error"
   },
   "overrides": [
     {
diff --git a/package-lock.json b/package-lock.json
index cbe12b20..f41b1125 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -50,6 +50,7 @@
         "eslint-plugin-import": "^2.28.1",
         "eslint-plugin-jsx-a11y": "^6.7.1",
         "eslint-plugin-n": "^16.1.0",
+        "eslint-plugin-prettier": "^5.0.1",
         "eslint-plugin-promise": "^6.1.1",
         "eslint-plugin-react": "^7.33.2",
         "eslint-plugin-react-hooks": "^4.6.0",
@@ -1885,6 +1886,26 @@
       "resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.8.4.tgz",
       "integrity": "sha512-kB+NJ5Br56ZhElKsf0pM7/PQfrDdDVMRz8f0JM6eVOGE+L89z9hwcst9QvWBBnazzuqGTGtPsJNZoQ1JdNiGSQ=="
     },
+    "node_modules/@pkgr/utils": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz",
+      "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==",
+      "dev": true,
+      "dependencies": {
+        "cross-spawn": "^7.0.3",
+        "fast-glob": "^3.3.0",
+        "is-glob": "^4.0.3",
+        "open": "^9.1.0",
+        "picocolors": "^1.0.0",
+        "tslib": "^2.6.0"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/unts"
+      }
+    },
     "node_modules/@reduxjs/toolkit": {
       "version": "1.9.6",
       "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.6.tgz",
@@ -3208,6 +3229,15 @@
         "tweetnacl": "^0.14.3"
       }
     },
+    "node_modules/big-integer": {
+      "version": "1.6.51",
+      "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
+      "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.6"
+      }
+    },
     "node_modules/binary-extensions": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -3239,6 +3269,18 @@
       "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
       "dev": true
     },
+    "node_modules/bplist-parser": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz",
+      "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==",
+      "dev": true,
+      "dependencies": {
+        "big-integer": "^1.6.44"
+      },
+      "engines": {
+        "node": ">= 5.10.0"
+      }
+    },
     "node_modules/brace-expansion": {
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -3347,6 +3389,21 @@
         "semver": "^7.0.0"
       }
     },
+    "node_modules/bundle-name": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz",
+      "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==",
+      "dev": true,
+      "dependencies": {
+        "run-applescript": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/busboy": {
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -4379,6 +4436,150 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/default-browser": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz",
+      "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==",
+      "dev": true,
+      "dependencies": {
+        "bundle-name": "^3.0.0",
+        "default-browser-id": "^3.0.0",
+        "execa": "^7.1.1",
+        "titleize": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=14.16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/default-browser-id": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz",
+      "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==",
+      "dev": true,
+      "dependencies": {
+        "bplist-parser": "^0.2.0",
+        "untildify": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/default-browser/node_modules/execa": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz",
+      "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==",
+      "dev": true,
+      "dependencies": {
+        "cross-spawn": "^7.0.3",
+        "get-stream": "^6.0.1",
+        "human-signals": "^4.3.0",
+        "is-stream": "^3.0.0",
+        "merge-stream": "^2.0.0",
+        "npm-run-path": "^5.1.0",
+        "onetime": "^6.0.0",
+        "signal-exit": "^3.0.7",
+        "strip-final-newline": "^3.0.0"
+      },
+      "engines": {
+        "node": "^14.18.0 || ^16.14.0 || >=18.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sindresorhus/execa?sponsor=1"
+      }
+    },
+    "node_modules/default-browser/node_modules/human-signals": {
+      "version": "4.3.1",
+      "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz",
+      "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=14.18.0"
+      }
+    },
+    "node_modules/default-browser/node_modules/is-stream": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
+      "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
+      "dev": true,
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/default-browser/node_modules/mimic-fn": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
+      "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/default-browser/node_modules/npm-run-path": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz",
+      "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==",
+      "dev": true,
+      "dependencies": {
+        "path-key": "^4.0.0"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/default-browser/node_modules/onetime": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
+      "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
+      "dev": true,
+      "dependencies": {
+        "mimic-fn": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/default-browser/node_modules/path-key": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+      "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/default-browser/node_modules/strip-final-newline": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
+      "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/defaults": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
@@ -4404,6 +4605,18 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/define-lazy-prop": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
+      "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/define-properties": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
@@ -5211,6 +5424,35 @@
         "eslint": ">=7.0.0"
       }
     },
+    "node_modules/eslint-plugin-prettier": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.1.tgz",
+      "integrity": "sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==",
+      "dev": true,
+      "dependencies": {
+        "prettier-linter-helpers": "^1.0.0",
+        "synckit": "^0.8.5"
+      },
+      "engines": {
+        "node": "^14.18.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/prettier"
+      },
+      "peerDependencies": {
+        "@types/eslint": ">=8.0.0",
+        "eslint": ">=8.0.0",
+        "prettier": ">=3.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/eslint": {
+          "optional": true
+        },
+        "eslint-config-prettier": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/eslint-plugin-promise": {
       "version": "6.1.1",
       "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz",
@@ -5730,6 +5972,12 @@
       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
       "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
     },
+    "node_modules/fast-diff": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
+      "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
+      "dev": true
+    },
     "node_modules/fast-glob": {
       "version": "3.3.1",
       "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
@@ -6882,6 +7130,21 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/is-docker": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
+      "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
+      "dev": true,
+      "bin": {
+        "is-docker": "cli.js"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/is-extglob": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -6944,6 +7207,24 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/is-inside-container": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
+      "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
+      "dev": true,
+      "dependencies": {
+        "is-docker": "^3.0.0"
+      },
+      "bin": {
+        "is-inside-container": "cli.js"
+      },
+      "engines": {
+        "node": ">=14.16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/is-installed-globally": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz",
@@ -7206,6 +7487,33 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/is-wsl": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
+      "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+      "dev": true,
+      "dependencies": {
+        "is-docker": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-wsl/node_modules/is-docker": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+      "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
+      "dev": true,
+      "bin": {
+        "is-docker": "cli.js"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/isarray": {
       "version": "2.0.5",
       "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
@@ -9784,6 +10092,24 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/open": {
+      "version": "9.1.0",
+      "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz",
+      "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==",
+      "dev": true,
+      "dependencies": {
+        "default-browser": "^4.0.0",
+        "define-lazy-prop": "^3.0.0",
+        "is-inside-container": "^1.0.0",
+        "is-wsl": "^2.2.0"
+      },
+      "engines": {
+        "node": ">=14.16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/optionator": {
       "version": "0.9.3",
       "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
@@ -10282,6 +10608,18 @@
         "url": "https://github.com/prettier/prettier?sponsor=1"
       }
     },
+    "node_modules/prettier-linter-helpers": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+      "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+      "dev": true,
+      "dependencies": {
+        "fast-diff": "^1.1.2"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
     "node_modules/prettier-plugin-tailwindcss": {
       "version": "0.5.6",
       "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.5.6.tgz",
@@ -11077,6 +11415,21 @@
         "url": "https://github.com/sponsors/isaacs"
       }
     },
+    "node_modules/run-applescript": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz",
+      "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==",
+      "dev": true,
+      "dependencies": {
+        "execa": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/run-async": {
       "version": "2.4.1",
       "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
@@ -11720,6 +12073,22 @@
       "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
       "dev": true
     },
+    "node_modules/synckit": {
+      "version": "0.8.5",
+      "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz",
+      "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==",
+      "dev": true,
+      "dependencies": {
+        "@pkgr/utils": "^2.3.1",
+        "tslib": "^2.5.0"
+      },
+      "engines": {
+        "node": "^14.18.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/unts"
+      }
+    },
     "node_modules/tailwind-merge": {
       "version": "1.14.0",
       "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz",
@@ -11841,6 +12210,18 @@
         "readable-stream": "3"
       }
     },
+    "node_modules/titleize": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz",
+      "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/tmp": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
diff --git a/package.json b/package.json
index c359766c..c6c19cd6 100644
--- a/package.json
+++ b/package.json
@@ -64,6 +64,7 @@
     "eslint-plugin-import": "^2.28.1",
     "eslint-plugin-jsx-a11y": "^6.7.1",
     "eslint-plugin-n": "^16.1.0",
+    "eslint-plugin-prettier": "^5.0.1",
     "eslint-plugin-promise": "^6.1.1",
     "eslint-plugin-react": "^7.33.2",
     "eslint-plugin-react-hooks": "^4.6.0",
diff --git a/prettier.config.js b/prettier.config.js
index fd41c7ea..eec5dca5 100644
--- a/prettier.config.js
+++ b/prettier.config.js
@@ -6,6 +6,7 @@ const config = {
   plugins: [import('prettier-plugin-tailwindcss')],
   tailwindConfig: './tailwind.config.ts',
   tailwindFunctions: ['twMerge'],
+  tabWidth: 2,
 };
 
 module.exports = config;
diff --git a/src/components/Map/MapViewer/utils/config/getMapTileUrl.ts b/src/components/Map/MapViewer/utils/config/getMapTileUrl.ts
new file mode 100644
index 00000000..0ba5eed4
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/config/getMapTileUrl.ts
@@ -0,0 +1,15 @@
+import { BASE_MAP_IMAGES_URL } from '@/constants';
+
+export const getMapTileUrl = ({
+  projectDirectory,
+  currentBackgroundImagePath,
+}: {
+  projectDirectory?: string;
+  currentBackgroundImagePath: string;
+}): string => {
+  if (!projectDirectory) {
+    return '';
+  }
+
+  return `${BASE_MAP_IMAGES_URL}/map_images/${projectDirectory}/${currentBackgroundImagePath}/{z}/{x}/{y}.PNG`;
+};
diff --git a/src/components/Map/MapViewer/utils/config/useOlMapConfig.ts b/src/components/Map/MapViewer/utils/config/useOlMapConfig.ts
index 42d07a05..2a584d87 100644
--- a/src/components/Map/MapViewer/utils/config/useOlMapConfig.ts
+++ b/src/components/Map/MapViewer/utils/config/useOlMapConfig.ts
@@ -1,5 +1,4 @@
 /* eslint-disable no-magic-numbers */
-import { BASE_MAP_IMAGES_URL } from '@/constants';
 import { OPTIONS } from '@/constants/map';
 import { currentBackgroundImagePathSelector } from '@/redux/backgrounds/background.selectors';
 import { mapDataPositionSelector, mapDataSizeSelector } from '@/redux/map/map.selectors';
@@ -12,6 +11,7 @@ import TileLayer from 'ol/layer/Tile';
 import { XYZ } from 'ol/source';
 import { useMemo } from 'react';
 import { useSelector } from 'react-redux';
+import { getMapTileUrl } from './getMapTileUrl';
 
 interface UseOlMapConfigResult {
   view: View;
@@ -45,18 +45,18 @@ export const useOlMapConfig = (): UseOlMapConfigResult => {
   );
 
   const tileLayer = useMemo(
-    () =>
+    (): TileLayer<XYZ> =>
       new TileLayer({
         visible: true,
         source: new XYZ({
-          url: `${BASE_MAP_IMAGES_URL}/map_images/${project?.directory}/${currentBackgroundImagePath}/{z}/{x}/{y}.PNG`,
+          url: getMapTileUrl({ projectDirectory: project?.directory, currentBackgroundImagePath }),
           maxZoom: mapSize.maxZoom,
           minZoom: mapSize.minZoom,
           tileSize: mapSize.tileSize,
           wrapX: OPTIONS.wrapXInTileLayer,
         }),
       }),
-    [mapSize, currentBackgroundImagePath, project],
+    [mapSize, currentBackgroundImagePath, project?.directory],
   );
 
   return {
diff --git a/src/components/Map/MapViewer/utils/init/useFirstStage.ts b/src/components/Map/MapViewer/utils/init/useFirstStage.ts
deleted file mode 100644
index 0b8af2ed..00000000
--- a/src/components/Map/MapViewer/utils/init/useFirstStage.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { PROJECT_ID } from '@/constants';
-import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
-import { getProjectById } from '@/redux/project/project.thunks';
-import { mapLoadingFirstStageInitializedSelector } from '@/redux/root/mapStages.selectors';
-import { useCallback, useEffect } from 'react';
-import { useSelector } from 'react-redux';
-
-export const useFirstStage = (): void => {
-  const dispatch = useAppDispatch();
-  const isInitialized = useSelector(mapLoadingFirstStageInitializedSelector);
-
-  const initProjectData = useCallback((): void => {
-    dispatch(getProjectById(PROJECT_ID));
-  }, [dispatch]);
-
-  useEffect(() => {
-    if (isInitialized) {
-      return;
-    }
-
-    initProjectData();
-  }, [initProjectData, isInitialized]);
-};
diff --git a/src/components/Map/MapViewer/utils/init/useOlMapInit.ts b/src/components/Map/MapViewer/utils/init/useOlMapInit.ts
deleted file mode 100644
index 2e993841..00000000
--- a/src/components/Map/MapViewer/utils/init/useOlMapInit.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { useFirstStage } from './useFirstStage';
-import { useSecondStage } from './useSecondStage';
-import { useThirdStage } from './useThirdStage';
-
-export const useOlMapInit = (): void => {
-  useFirstStage();
-  useSecondStage();
-  useThirdStage();
-};
diff --git a/src/components/Map/MapViewer/utils/init/useSecondStage.ts b/src/components/Map/MapViewer/utils/init/useSecondStage.ts
deleted file mode 100644
index 360a56c8..00000000
--- a/src/components/Map/MapViewer/utils/init/useSecondStage.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { getAllBackgroundsByProjectId } from '@/redux/backgrounds/backgrounds.thunks';
-import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
-import { getAllModelsByProjectId } from '@/redux/models/models.thunks';
-import { getAllPublicOverlaysByProjectId } from '@/redux/overlays/overlays.thunks';
-import { projectDataSelector } from '@/redux/project/project.selectors';
-import {
-  mapLoadingFirstStageCompletedSelector,
-  mapLoadingSecondStageInitializedSelector,
-} from '@/redux/root/mapStages.selectors';
-import { AppDispatch } from '@/redux/store';
-import { Project } from '@/types/models';
-import { useCallback, useEffect } from 'react';
-import { useSelector } from 'react-redux';
-
-const getMapDataAction =
-  (project: Project) =>
-    (dispatch: AppDispatch): void => {
-      dispatch(getAllBackgroundsByProjectId(project.projectId));
-      dispatch(getAllPublicOverlaysByProjectId(project.projectId));
-      dispatch(getAllModelsByProjectId(project.projectId));
-    };
-
-export const useSecondStage = (): void => {
-  const dispatch = useAppDispatch();
-  const project = useSelector(projectDataSelector);
-  const isPreviousCompleted = useSelector(mapLoadingFirstStageCompletedSelector);
-  const isInitialized = useSelector(mapLoadingSecondStageInitializedSelector);
-
-  const initMapData = useCallback((): void => {
-    if (!project) {
-      return;
-    }
-
-    dispatch(getMapDataAction(project));
-  }, [dispatch, project]);
-
-  useEffect(() => {
-    if (!isPreviousCompleted || isInitialized) {
-      return;
-    }
-
-    initMapData();
-  }, [initMapData, isInitialized, isPreviousCompleted]);
-};
diff --git a/src/components/Map/MapViewer/utils/init/useThirdStage.ts b/src/components/Map/MapViewer/utils/init/useThirdStage.ts
deleted file mode 100644
index bb6572a0..00000000
--- a/src/components/Map/MapViewer/utils/init/useThirdStage.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { backgroundsDataSelector } from '@/redux/backgrounds/background.selectors';
-import { mapDataSelector } from '@/redux/map/map.selectors';
-import { modelsDataSelector } from '@/redux/models/models.selectors';
-import { mapLoadingAllStagesCompletedSelector } from '@/redux/root/mapStages.selectors';
-import { useHandleMapChange } from '@/utils/map/useHandleMapChange';
-import { useCallback, useEffect } from 'react';
-import { useSelector } from 'react-redux';
-
-const FIRST = 0;
-
-export const useThirdStage = (): void => {
-  const map = useSelector(mapDataSelector);
-  const models = useSelector(modelsDataSelector);
-  const backgrounds = useSelector(backgroundsDataSelector);
-  const loadingCompleted = useSelector(mapLoadingAllStagesCompletedSelector);
-  const handleMapChange = useHandleMapChange();
-
-  const setDefaultMapModelData = useCallback(() => {
-    const defaultBackground = backgrounds?.[FIRST];
-    const defaultModel = models?.[FIRST];
-    if (map.modelId || !defaultModel || !defaultBackground) {
-      return;
-    }
-
-    handleMapChange({
-      model: defaultModel,
-      background: defaultBackground,
-    });
-  }, [backgrounds, models, map, handleMapChange]);
-
-  useEffect(() => {
-    if (!loadingCompleted) {
-      return;
-    }
-
-    setDefaultMapModelData();
-  }, [setDefaultMapModelData, loadingCompleted]);
-};
diff --git a/src/components/Map/MapViewer/utils/useOlMap.ts b/src/components/Map/MapViewer/utils/useOlMap.ts
index c17fc281..ca82591a 100644
--- a/src/components/Map/MapViewer/utils/useOlMap.ts
+++ b/src/components/Map/MapViewer/utils/useOlMap.ts
@@ -2,7 +2,6 @@ import Map from 'ol/Map';
 import React, { MutableRefObject, useEffect, useState } from 'react';
 import { MapInstance } from '../MapViewer.types';
 import { useOlMapConfig } from './config/useOlMapConfig';
-import { useOlMapInit } from './init/useOlMapInit';
 
 interface UseOlMapInput {
   target?: HTMLElement;
@@ -18,7 +17,6 @@ export const useOlMap: UseOlMap = ({ target } = {}) => {
   const mapRef = React.useRef<null | HTMLDivElement>(null);
   const [mapInstance, setMapInstance] = useState<MapInstance>(undefined);
   const mapConfig = useOlMapConfig();
-  useOlMapInit();
 
   useEffect(() => {
     // checking if innerHTML is empty due to possibility of target element cloning by openlayers map instance
diff --git a/src/components/SPA/MinervaSPA.component.tsx b/src/components/SPA/MinervaSPA.component.tsx
index c9b53815..6703ea8c 100644
--- a/src/components/SPA/MinervaSPA.component.tsx
+++ b/src/components/SPA/MinervaSPA.component.tsx
@@ -1,10 +1,15 @@
-import { Manrope } from '@next/font/google';
-import { twMerge } from 'tailwind-merge';
-import { Map } from '@/components/Map';
 import { FunctionalArea } from '@/components/FunctionalArea';
+import { Map } from '@/components/Map';
+import { PROJECT_ID } from '@/constants';
 import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
+import { initMapData } from '@/redux/map/map.thunks';
+import { getProjectById } from '@/redux/project/project.thunks';
+import { initDataLoadingInitialized } from '@/redux/root/init.selectors';
+import { AppDispatch } from '@/redux/store';
+import { Manrope } from '@next/font/google';
 import { useEffect } from 'react';
-import { getModels } from '@/redux/models/models.thunks';
+import { useSelector } from 'react-redux';
+import { twMerge } from 'tailwind-merge';
 
 const manrope = Manrope({
   variable: '--font-manrope',
@@ -13,12 +18,25 @@ const manrope = Manrope({
   subsets: ['latin'],
 });
 
+/* prettier-ignore */
+const getInitStoreData =
+  () =>
+    (dispatch: AppDispatch): void => {
+      dispatch(getProjectById(PROJECT_ID));
+      dispatch(initMapData());
+    };
+
 export const MinervaSPA = (): JSX.Element => {
   const dispatch = useAppDispatch();
+  const storeInitialized = useSelector(initDataLoadingInitialized);
 
   useEffect(() => {
-    dispatch(getModels());
-  }, [dispatch]);
+    if (storeInitialized) {
+      return;
+    }
+
+    dispatch(getInitStoreData());
+  }, [dispatch, storeInitialized]);
 
   return (
     <div className={twMerge('relative', manrope.variable)}>
diff --git a/src/redux/backgrounds/background.selectors.ts b/src/redux/backgrounds/background.selectors.ts
index 01b6ed81..16b23397 100644
--- a/src/redux/backgrounds/background.selectors.ts
+++ b/src/redux/backgrounds/background.selectors.ts
@@ -6,7 +6,7 @@ export const backgroundsSelector = createSelector(rootSelector, state => state.b
 
 export const backgroundsDataSelector = createSelector(
   backgroundsSelector,
-  backgrounds => backgrounds.data || [],
+  backgrounds => backgrounds?.data || [],
 );
 
 export const currentBackgroundSelector = createSelector(
diff --git a/src/redux/map/map.middleware.ts b/src/redux/map/map.middleware.ts
new file mode 100644
index 00000000..dc1e99e7
--- /dev/null
+++ b/src/redux/map/map.middleware.ts
@@ -0,0 +1,46 @@
+import { MapModel } from '@/types/models';
+import { getUpdatedMapData } from '@/utils/getUpdatedMapData';
+import { Middleware, MiddlewareAPI, PayloadAction } from '@reduxjs/toolkit';
+import { modelsDataSelector } from '../models/models.selectors';
+import type { AppDispatch, RootState } from '../store';
+import { setMapData } from './map.slice';
+import { InitMapDataActionPayload, SetMapDataActionPayload } from './map.types';
+
+type AllowedAction = PayloadAction<SetMapDataActionPayload | InitMapDataActionPayload>;
+
+const ALLOWED_ACTIONS = ['map/setMapData', 'map/initMapData'];
+
+const checkIfIsActionValid = (action: AllowedAction, state: RootState): boolean => {
+  const isAllowedAction = ALLOWED_ACTIONS.some(allowedAction =>
+    action.type.includes(allowedAction),
+  );
+  const isModelIdTheSame = state.map.data.modelId === action.payload?.modelId;
+
+  return isAllowedAction && !isModelIdTheSame;
+};
+
+const getUpdatedModel = (action: AllowedAction, state: RootState): MapModel | undefined => {
+  const models = modelsDataSelector(state);
+  return models.find(model => model.idObject === action?.payload?.modelId);
+};
+
+/* prettier-ignore */
+export const mapMiddleware: Middleware =
+  ({ getState, dispatch }: MiddlewareAPI<AppDispatch, RootState>) =>
+    (next: AppDispatch) =>
+    // eslint-disable-next-line consistent-return
+      (action: AllowedAction) => {
+        const state = getState();
+        const isActionValid = checkIfIsActionValid(action, state);
+        const updatedModel = getUpdatedModel(action, state);
+        const returnValue = next(action);
+
+        if (!isActionValid || !updatedModel) {
+          return returnValue;
+        }
+
+        const updatedMapData = getUpdatedMapData({ model: updatedModel });
+        dispatch(setMapData(updatedMapData));
+
+        return returnValue;
+      };
diff --git a/src/redux/map/map.reducers.ts b/src/redux/map/map.reducers.ts
index 95d76325..51f7faab 100644
--- a/src/redux/map/map.reducers.ts
+++ b/src/redux/map/map.reducers.ts
@@ -1,9 +1,21 @@
-import { PayloadAction } from '@reduxjs/toolkit';
-import { MapData, MapState } from './map.types';
+import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
+import { initMapData } from './map.thunks';
+import { MapState, SetMapDataAction } from './map.types';
 
-export const setMapDataReducer = (
-  state: MapState,
-  action: PayloadAction<Partial<MapData> | undefined>,
-): void => {
+export const setMapDataReducer = (state: MapState, action: SetMapDataAction): void => {
   state.data = { ...state.data, ...action.payload };
 };
+
+export const getMapReducers = (builder: ActionReducerMapBuilder<MapState>): void => {
+  builder.addCase(initMapData.pending, state => {
+    state.loading = 'pending';
+  });
+  builder.addCase(initMapData.fulfilled, (state, action) => {
+    state.data = { ...state.data, ...action.payload };
+    state.loading = 'succeeded';
+  });
+  builder.addCase(initMapData.rejected, state => {
+    state.loading = 'failed';
+    // TODO to discuss manage state of failure
+  });
+};
diff --git a/src/redux/map/map.slice.ts b/src/redux/map/map.slice.ts
index 62b95df2..3565dc25 100644
--- a/src/redux/map/map.slice.ts
+++ b/src/redux/map/map.slice.ts
@@ -1,6 +1,6 @@
 import { createSlice } from '@reduxjs/toolkit';
 import { MAP_DATA_INITIAL_STATE } from './map.constants';
-import { setMapDataReducer } from './map.reducers';
+import { getMapReducers, setMapDataReducer } from './map.reducers';
 import { MapState } from './map.types';
 
 const initialState: MapState = {
@@ -15,6 +15,9 @@ const mapSlice = createSlice({
   reducers: {
     setMapData: setMapDataReducer,
   },
+  extraReducers: builder => {
+    getMapReducers(builder);
+  },
 });
 
 export const { setMapData } = mapSlice.actions;
diff --git a/src/redux/map/map.thunks.ts b/src/redux/map/map.thunks.ts
new file mode 100644
index 00000000..481d5cfb
--- /dev/null
+++ b/src/redux/map/map.thunks.ts
@@ -0,0 +1,31 @@
+import { PROJECT_ID } from '@/constants';
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { backgroundsDataSelector } from '../backgrounds/background.selectors';
+import { getAllBackgroundsByProjectId } from '../backgrounds/backgrounds.thunks';
+import { modelsDataSelector } from '../models/models.selectors';
+import { getModels } from '../models/models.thunks';
+import { getAllPublicOverlaysByProjectId } from '../overlays/overlays.thunks';
+import type { AppDispatch, RootState } from '../store';
+import { InitMapDataActionPayload } from './map.types';
+
+const FIRST = 0;
+
+export const initMapData = createAsyncThunk<
+  InitMapDataActionPayload,
+  void,
+  { dispatch: AppDispatch; state: RootState }
+>('map/initMapData', async (_, { dispatch, getState }): Promise<InitMapDataActionPayload> => {
+  await Promise.all([
+    dispatch(getAllBackgroundsByProjectId(PROJECT_ID)),
+    dispatch(getAllPublicOverlaysByProjectId(PROJECT_ID)),
+    dispatch(getModels()),
+  ]);
+
+  const state = getState();
+  const models = modelsDataSelector(state);
+  const backgrounds = backgroundsDataSelector(state);
+  const modelId = models[FIRST].idObject;
+  const backgroundId = backgrounds[FIRST].id;
+
+  return { modelId, backgroundId };
+});
diff --git a/src/redux/map/map.types.ts b/src/redux/map/map.types.ts
index ac46d776..108648dc 100644
--- a/src/redux/map/map.types.ts
+++ b/src/redux/map/map.types.ts
@@ -1,5 +1,6 @@
 import { FetchDataState } from '@/types/fetchDataState';
 import { Point } from '@/types/map';
+import { PayloadAction } from '@reduxjs/toolkit';
 
 export interface MapSize {
   width: number;
@@ -24,3 +25,11 @@ export type MapData = {
 };
 
 export type MapState = FetchDataState<MapData, MapData>;
+
+export type SetMapDataActionPayload = Partial<MapData> | undefined;
+
+export type SetMapDataAction = PayloadAction<SetMapDataActionPayload>;
+
+export type InitMapDataActionPayload = { modelId: number; backgroundId: number };
+
+export type InitMapDataAction = PayloadAction<SetMapDataAction>;
diff --git a/src/redux/models/models.thunks.test.ts b/src/redux/models/models.thunks.test.ts
index 3d85a15e..a8daf71d 100644
--- a/src/redux/models/models.thunks.test.ts
+++ b/src/redux/models/models.thunks.test.ts
@@ -1,12 +1,12 @@
-import { HttpStatusCode } from 'axios';
-import { mockNetworkResponse } from '@/utils/mockNetworkResponse';
+import { modelsFixture } from '@/models/fixtures/modelsFixture';
+import { apiPath } from '@/redux/apiPath';
+import { ModelsState } from '@/redux/models/models.types';
 import {
   ToolkitStoreWithSingleSlice,
   createStoreInstanceUsingSliceReducer,
 } from '@/utils/createStoreInstanceUsingSliceReducer';
-import { ModelsState } from '@/redux/models/models.types';
-import { apiPath } from '@/redux/apiPath';
-import { modelsFixture } from '@/models/fixtures/modelsFixture';
+import { mockNetworkResponse } from '@/utils/mockNetworkResponse';
+import { HttpStatusCode } from 'axios';
 import modelsReducer from './models.slice';
 import { getModels } from './models.thunks';
 
diff --git a/src/redux/project/project.selectors.ts b/src/redux/project/project.selectors.ts
index 9ba0ec03..2725956a 100644
--- a/src/redux/project/project.selectors.ts
+++ b/src/redux/project/project.selectors.ts
@@ -3,4 +3,4 @@ import { rootSelector } from '../root/root.selectors';
 
 export const projectSelector = createSelector(rootSelector, state => state.project);
 
-export const projectDataSelector = createSelector(projectSelector, project => project.data);
+export const projectDataSelector = createSelector(projectSelector, project => project?.data);
diff --git a/src/redux/root/init.selectors.ts b/src/redux/root/init.selectors.ts
new file mode 100644
index 00000000..82176735
--- /dev/null
+++ b/src/redux/root/init.selectors.ts
@@ -0,0 +1,13 @@
+import { createSelector } from '@reduxjs/toolkit';
+import { backgroundsSelector } from '../backgrounds/background.selectors';
+import { modelsSelector } from '../models/models.selectors';
+import { overlaysSelector } from '../overlays/overlays.selectors';
+import { projectSelector } from '../project/project.selectors';
+
+export const initDataLoadingInitialized = createSelector(
+  projectSelector,
+  backgroundsSelector,
+  modelsSelector,
+  overlaysSelector,
+  (...selectors) => selectors.every(selector => selector.loading !== 'idle'),
+);
diff --git a/src/redux/root/mapStages.selectors.ts b/src/redux/root/mapStages.selectors.ts
deleted file mode 100644
index cda0f3b2..00000000
--- a/src/redux/root/mapStages.selectors.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { createSelector } from '@reduxjs/toolkit';
-import { backgroundsSelector } from '../backgrounds/background.selectors';
-import { modelsSelector } from '../models/models.selectors';
-import { overlaysSelector } from '../overlays/overlays.selectors';
-import { projectSelector } from '../project/project.selectors';
-
-export const mapLoadingFirstStageInitializedSelector = createSelector(
-  projectSelector,
-  project => project.loading !== 'idle',
-);
-
-export const mapLoadingFirstStageCompletedSelector = createSelector(
-  projectSelector,
-  project => project.loading === 'succeeded',
-);
-
-export const mapLoadingSecondStageInitializedSelector = createSelector(
-  backgroundsSelector,
-  modelsSelector,
-  overlaysSelector,
-  (backgrounds, models, overlays) =>
-    [backgrounds.loading, models.loading, overlays.loading].every(loading => loading !== 'idle'),
-);
-
-export const mapLoadingSecondStageCompletedSelector = createSelector(
-  backgroundsSelector,
-  modelsSelector,
-  overlaysSelector,
-  (backgrounds, models, overlays) =>
-    [backgrounds.loading, models.loading, overlays.loading].every(
-      loading => loading === 'succeeded',
-    ),
-);
-
-export const mapLoadingAllStagesCompletedSelector = createSelector(
-  mapLoadingFirstStageCompletedSelector,
-  mapLoadingSecondStageCompletedSelector,
-  (firstStageCompleted, secondStageCompleted) =>
-    [firstStageCompleted, secondStageCompleted].every(completed => completed === true),
-);
diff --git a/src/redux/store.ts b/src/redux/store.ts
index 0c59b4e1..e1ecab61 100644
--- a/src/redux/store.ts
+++ b/src/redux/store.ts
@@ -9,7 +9,8 @@ import modelsReducer from '@/redux/models/models.slice';
 import overlaysReducer from '@/redux/overlays/overlays.slice';
 import projectReducer from '@/redux/project/project.slice';
 import searchReducer from '@/redux/search/search.slice';
-import { configureStore } from '@reduxjs/toolkit';
+import { applyMiddleware, configureStore } from '@reduxjs/toolkit';
+import { mapMiddleware } from './map/map.middleware';
 
 export const reducers = {
   search: searchReducer,
@@ -28,6 +29,7 @@ export const reducers = {
 export const store = configureStore({
   reducer: reducers,
   devTools: true,
+  enhancers: [applyMiddleware(mapMiddleware)],
 });
 
 export type StoreType = typeof store;
diff --git a/src/utils/getUpdatedMapData.ts b/src/utils/getUpdatedMapData.ts
new file mode 100644
index 00000000..7bbc1329
--- /dev/null
+++ b/src/utils/getUpdatedMapData.ts
@@ -0,0 +1,29 @@
+import { DEFAULT_ZOOM } from '@/constants/map';
+import { MapData } from '@/redux/map/map.types';
+import { MapModel } from '@/types/models';
+
+interface GetUpdatedMapDataArgs {
+  model: MapModel;
+}
+
+type GetUpdatedMapDataResult = Pick<MapData, 'modelId' | 'size' | 'position'> & {
+  backgroundId?: number;
+};
+
+const HALF = 2;
+
+export const getUpdatedMapData = ({ model }: GetUpdatedMapDataArgs): GetUpdatedMapDataResult => ({
+  modelId: model.idObject,
+  size: {
+    width: model.width,
+    height: model.height,
+    tileSize: model.tileSize,
+    minZoom: model.minZoom,
+    maxZoom: model.maxZoom,
+  },
+  position: {
+    x: model.defaultCenterX || model.width / HALF,
+    y: model.defaultCenterY || model.height / HALF,
+    z: model.defaultZoomLevel || DEFAULT_ZOOM,
+  },
+});
diff --git a/src/utils/map/useHandleMapChange.ts b/src/utils/map/useHandleMapChange.ts
deleted file mode 100644
index 94bcaf91..00000000
--- a/src/utils/map/useHandleMapChange.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { DEFAULT_ZOOM } from '@/constants/map';
-import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
-import { setMapData } from '@/redux/map/map.slice';
-import { MapBackground, MapModel } from '@/types/models';
-
-interface HandleMapChangeArgs {
-  model: MapModel;
-  background?: MapBackground;
-}
-
-type UseHandleMapChangeFunction = (args: HandleMapChangeArgs) => void;
-
-const HALF = 2;
-
-export const useHandleMapChange = (): UseHandleMapChangeFunction => {
-  const dispatch = useAppDispatch();
-
-  const handleMapChange = ({ model, background }: HandleMapChangeArgs): void => {
-    dispatch(
-      setMapData({
-        modelId: model.idObject,
-        backgroundId: background?.id,
-        size: {
-          width: model.width,
-          height: model.height,
-          tileSize: model.tileSize,
-          minZoom: model.minZoom,
-          maxZoom: model.maxZoom,
-        },
-        position: {
-          x: model.defaultCenterX || model.width / HALF,
-          y: model.defaultCenterY || model.height / HALF,
-          z: model.defaultZoomLevel || DEFAULT_ZOOM,
-        },
-      }),
-    );
-  };
-
-  return handleMapChange;
-};
diff --git a/yarn.lock b/yarn.lock
index 4a3561b2..c865fa74 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -878,6 +878,18 @@
   "resolved" "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.8.4.tgz"
   "version" "3.8.4"
 
+"@pkgr/utils@^2.3.1":
+  "integrity" "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw=="
+  "resolved" "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz"
+  "version" "2.4.2"
+  dependencies:
+    "cross-spawn" "^7.0.3"
+    "fast-glob" "^3.3.0"
+    "is-glob" "^4.0.3"
+    "open" "^9.1.0"
+    "picocolors" "^1.0.0"
+    "tslib" "^2.6.0"
+
 "@reduxjs/toolkit@^1.9.6":
   "integrity" "sha512-Gc4ikl90ORF4viIdAkY06JNUnODjKfGxZRwATM30EdHq8hLSVoSrwXne5dd739yenP5bJxAX7tLuOWK5RPGtrw=="
   "resolved" "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.6.tgz"
@@ -1780,6 +1792,11 @@
   dependencies:
     "tweetnacl" "^0.14.3"
 
+"big-integer@^1.6.44":
+  "integrity" "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg=="
+  "resolved" "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz"
+  "version" "1.6.51"
+
 "binary-extensions@^2.0.0":
   "integrity" "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA=="
   "resolved" "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz"
@@ -1804,6 +1821,13 @@
   "resolved" "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz"
   "version" "3.7.2"
 
+"bplist-parser@^0.2.0":
+  "integrity" "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw=="
+  "resolved" "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz"
+  "version" "0.2.0"
+  dependencies:
+    "big-integer" "^1.6.44"
+
 "brace-expansion@^1.1.7":
   "integrity" "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="
   "resolved" "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz"
@@ -1861,6 +1885,13 @@
   dependencies:
     "semver" "^7.0.0"
 
+"bundle-name@^3.0.0":
+  "integrity" "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw=="
+  "resolved" "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz"
+  "version" "3.0.0"
+  dependencies:
+    "run-applescript" "^5.0.0"
+
 "busboy@1.6.0":
   "integrity" "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="
   "resolved" "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz"
@@ -2437,6 +2468,11 @@
   "resolved" "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz"
   "version" "10.4.3"
 
+"decode-uri-component@^0.2.2":
+  "integrity" "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="
+  "resolved" "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz"
+  "version" "0.2.2"
+
 "dedent@^1.0.0":
   "integrity" "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg=="
   "resolved" "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz"
@@ -2481,6 +2517,24 @@
   "resolved" "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz"
   "version" "4.3.1"
 
+"default-browser-id@^3.0.0":
+  "integrity" "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA=="
+  "resolved" "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz"
+  "version" "3.0.0"
+  dependencies:
+    "bplist-parser" "^0.2.0"
+    "untildify" "^4.0.0"
+
+"default-browser@^4.0.0":
+  "integrity" "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA=="
+  "resolved" "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz"
+  "version" "4.0.0"
+  dependencies:
+    "bundle-name" "^3.0.0"
+    "default-browser-id" "^3.0.0"
+    "execa" "^7.1.1"
+    "titleize" "^3.0.0"
+
 "defaults@^1.0.3":
   "integrity" "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="
   "resolved" "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz"
@@ -2497,6 +2551,11 @@
     "gopd" "^1.0.1"
     "has-property-descriptors" "^1.0.0"
 
+"define-lazy-prop@^3.0.0":
+  "integrity" "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="
+  "resolved" "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz"
+  "version" "3.0.0"
+
 "define-properties@^1.1.3", "define-properties@^1.1.4", "define-properties@^1.2.0", "define-properties@^1.2.1":
   "integrity" "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="
   "resolved" "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz"
@@ -2954,6 +3013,14 @@
     "resolve" "^1.22.2"
     "semver" "^7.5.3"
 
+"eslint-plugin-prettier@^5.0.1":
+  "integrity" "sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg=="
+  "resolved" "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.1.tgz"
+  "version" "5.0.1"
+  dependencies:
+    "prettier-linter-helpers" "^1.0.0"
+    "synckit" "^0.8.5"
+
 "eslint-plugin-promise@^6.0.0", "eslint-plugin-promise@^6.1.1":
   "integrity" "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig=="
   "resolved" "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz"
@@ -3022,7 +3089,7 @@
   "resolved" "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz"
   "version" "3.4.3"
 
-"eslint@*", "eslint@^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8", "eslint@^3 || ^4 || ^5 || ^6 || ^7 || ^8", "eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "eslint@^6.0.0 || ^7.0.0 || ^8.0.0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^7.0.0 || ^8.0.0", "eslint@^7.23.0 || ^8.0.0", "eslint@^7.32.0 || ^8.2.0", "eslint@^7.5.0 || ^8.0.0", "eslint@^8.0.1", "eslint@^8.49.0", "eslint@>=7.0.0", "eslint@>=8":
+"eslint@*", "eslint@^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8", "eslint@^3 || ^4 || ^5 || ^6 || ^7 || ^8", "eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "eslint@^6.0.0 || ^7.0.0 || ^8.0.0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^7.0.0 || ^8.0.0", "eslint@^7.23.0 || ^8.0.0", "eslint@^7.32.0 || ^8.2.0", "eslint@^7.5.0 || ^8.0.0", "eslint@^8.0.1", "eslint@^8.49.0", "eslint@>=7.0.0", "eslint@>=8", "eslint@>=8.0.0":
   "integrity" "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg=="
   "resolved" "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz"
   "version" "8.50.0"
@@ -3133,6 +3200,21 @@
     "signal-exit" "^3.0.3"
     "strip-final-newline" "^2.0.0"
 
+"execa@^7.1.1":
+  "integrity" "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA=="
+  "resolved" "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz"
+  "version" "7.2.0"
+  dependencies:
+    "cross-spawn" "^7.0.3"
+    "get-stream" "^6.0.1"
+    "human-signals" "^4.3.0"
+    "is-stream" "^3.0.0"
+    "merge-stream" "^2.0.0"
+    "npm-run-path" "^5.1.0"
+    "onetime" "^6.0.0"
+    "signal-exit" "^3.0.7"
+    "strip-final-newline" "^3.0.0"
+
 "execa@4.1.0":
   "integrity" "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA=="
   "resolved" "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz"
@@ -3228,7 +3310,12 @@
   "resolved" "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
   "version" "3.1.3"
 
-"fast-glob@^3.2.12", "fast-glob@^3.2.5", "fast-glob@^3.2.9", "fast-glob@^3.3.1":
+"fast-diff@^1.1.2":
+  "integrity" "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="
+  "resolved" "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz"
+  "version" "1.3.0"
+
+"fast-glob@^3.2.12", "fast-glob@^3.2.5", "fast-glob@^3.2.9", "fast-glob@^3.3.0", "fast-glob@^3.3.1":
   "integrity" "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="
   "resolved" "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz"
   "version" "3.3.1"
@@ -3291,6 +3378,11 @@
   dependencies:
     "to-regex-range" "^5.0.1"
 
+"filter-obj@^1.1.0":
+  "integrity" "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ=="
+  "resolved" "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz"
+  "version" "1.1.0"
+
 "find-node-modules@^2.1.2":
   "integrity" "sha512-UC2I2+nx1ZuOBclWVNdcnbDR5dlrOdVb7xNjmT/lHE+LsgztWks3dG7boJ37yTS/venXw84B/mAW9uHVoC5QRg=="
   "resolved" "https://registry.npmjs.org/find-node-modules/-/find-node-modules-2.1.3.tgz"
@@ -4015,6 +4107,16 @@
   dependencies:
     "has-tostringtag" "^1.0.0"
 
+"is-docker@^2.0.0":
+  "integrity" "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="
+  "resolved" "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz"
+  "version" "2.2.1"
+
+"is-docker@^3.0.0":
+  "integrity" "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="
+  "resolved" "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz"
+  "version" "3.0.0"
+
 "is-extglob@^2.1.1":
   "integrity" "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="
   "resolved" "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz"
@@ -4056,6 +4158,13 @@
   dependencies:
     "is-extglob" "^2.1.1"
 
+"is-inside-container@^1.0.0":
+  "integrity" "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="
+  "resolved" "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz"
+  "version" "1.0.0"
+  dependencies:
+    "is-docker" "^3.0.0"
+
 "is-installed-globally@~0.4.0":
   "integrity" "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ=="
   "resolved" "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz"
@@ -4209,6 +4318,13 @@
   "resolved" "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz"
   "version" "1.0.2"
 
+"is-wsl@^2.2.0":
+  "integrity" "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="
+  "resolved" "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz"
+  "version" "2.2.0"
+  dependencies:
+    "is-docker" "^2.0.0"
+
 "isarray@^2.0.5":
   "integrity" "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="
   "resolved" "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz"
@@ -5243,7 +5359,12 @@
   "resolved" "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
   "version" "1.4.0"
 
-"next@13.4.19":
+"next-router-mock@^0.9.10":
+  "integrity" "sha512-bK6sRb/xGNFgHVUZuvuApn6KJBAKTPiP36A7a9mO77U4xQO5ukJx9WHlU67Tv8AuySd09pk0+Hu8qMVIAmLO6A=="
+  "resolved" "https://registry.npmjs.org/next-router-mock/-/next-router-mock-0.9.10.tgz"
+  "version" "0.9.10"
+
+"next@>=10.0.0", "next@13.4.19":
   "integrity" "sha512-HuPSzzAbJ1T4BD8e0bs6B9C1kWQ6gv8ykZoRWs5AQoiIuqbGHHdQO7Ljuvg05Q0Z24E2ABozHe6FxDvI6HfyAw=="
   "resolved" "https://registry.npmjs.org/next/-/next-13.4.19.tgz"
   "version" "13.4.19"
@@ -5440,6 +5561,16 @@
   dependencies:
     "mimic-fn" "^4.0.0"
 
+"open@^9.1.0":
+  "integrity" "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg=="
+  "resolved" "https://registry.npmjs.org/open/-/open-9.1.0.tgz"
+  "version" "9.1.0"
+  dependencies:
+    "default-browser" "^4.0.0"
+    "define-lazy-prop" "^3.0.0"
+    "is-inside-container" "^1.0.0"
+    "is-wsl" "^2.2.0"
+
 "optionator@^0.9.3":
   "integrity" "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg=="
   "resolved" "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz"
@@ -5708,12 +5839,19 @@
   "resolved" "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz"
   "version" "2.8.8"
 
+"prettier-linter-helpers@^1.0.0":
+  "integrity" "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w=="
+  "resolved" "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz"
+  "version" "1.0.0"
+  dependencies:
+    "fast-diff" "^1.1.2"
+
 "prettier-plugin-tailwindcss@^0.5.6":
   "integrity" "sha512-2Xgb+GQlkPAUCFi3sV+NOYcSI5XgduvDBL2Zt/hwJudeKXkyvRS65c38SB0yb9UB40+1rL83I6m0RtlOQ8eHdg=="
   "resolved" "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.5.6.tgz"
   "version" "0.5.6"
 
-"prettier@^3.0", "prettier@^3.0.3":
+"prettier@^3.0", "prettier@^3.0.3", "prettier@>=3.0.0":
   "integrity" "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg=="
   "resolved" "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz"
   "version" "3.0.3"
@@ -5817,6 +5955,16 @@
   dependencies:
     "side-channel" "^1.0.4"
 
+"query-string@7.1.3":
+  "integrity" "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg=="
+  "resolved" "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz"
+  "version" "7.1.3"
+  dependencies:
+    "decode-uri-component" "^0.2.2"
+    "filter-obj" "^1.1.0"
+    "split-on-first" "^1.0.0"
+    "strict-uri-encode" "^2.0.0"
+
 "querystringify@^2.1.1":
   "integrity" "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
   "resolved" "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz"
@@ -5897,7 +6045,7 @@
     "react-is" "^18.0.0"
     "use-sync-external-store" "^1.0.0"
 
-"react@^16.3.2 || ^17.0.0 || ^18.0.0", "react@^16.8 || ^17.0 || ^18.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0-0 || ^17.0.0 || ^18.0.0", "react@^16.9.0 || ^17.0.0 || ^18", "react@^18.0.0", "react@^18.2.0", "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", "react@18.2.0":
+"react@^16.3.2 || ^17.0.0 || ^18.0.0", "react@^16.8 || ^17.0 || ^18.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0-0 || ^17.0.0 || ^18.0.0", "react@^16.9.0 || ^17.0.0 || ^18", "react@^18.0.0", "react@^18.2.0", "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", "react@>=17.0.0", "react@18.2.0":
   "integrity" "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="
   "resolved" "https://registry.npmjs.org/react/-/react-18.2.0.tgz"
   "version" "18.2.0"
@@ -6124,6 +6272,13 @@
   dependencies:
     "glob" "^7.1.3"
 
+"run-applescript@^5.0.0":
+  "integrity" "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg=="
+  "resolved" "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz"
+  "version" "5.0.0"
+  dependencies:
+    "execa" "^5.0.0"
+
 "run-async@^2.4.0":
   "integrity" "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ=="
   "resolved" "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz"
@@ -6328,6 +6483,11 @@
   "resolved" "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.15.tgz"
   "version" "3.0.15"
 
+"split-on-first@^1.0.0":
+  "integrity" "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="
+  "resolved" "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz"
+  "version" "1.1.0"
+
 "split2@^3.0.0", "split2@^3.2.2":
   "integrity" "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg=="
   "resolved" "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz"
@@ -6374,6 +6534,11 @@
   "resolved" "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz"
   "version" "1.1.0"
 
+"strict-uri-encode@^2.0.0":
+  "integrity" "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ=="
+  "resolved" "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz"
+  "version" "2.0.0"
+
 "string_decoder@^1.1.1":
   "integrity" "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="
   "resolved" "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz"
@@ -6566,6 +6731,14 @@
   "resolved" "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz"
   "version" "3.2.4"
 
+"synckit@^0.8.5":
+  "integrity" "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q=="
+  "resolved" "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz"
+  "version" "0.8.5"
+  dependencies:
+    "@pkgr/utils" "^2.3.1"
+    "tslib" "^2.5.0"
+
 "tailwind-merge@^1.14.0":
   "integrity" "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ=="
   "resolved" "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz"
@@ -6654,6 +6827,11 @@
   dependencies:
     "readable-stream" "3"
 
+"titleize@^3.0.0":
+  "integrity" "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ=="
+  "resolved" "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz"
+  "version" "3.0.0"
+
 "tmp@^0.0.33":
   "integrity" "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw=="
   "resolved" "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz"
@@ -6751,7 +6929,7 @@
   "resolved" "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
   "version" "1.14.1"
 
-"tslib@^2.1.0", "tslib@^2.4.0":
+"tslib@^2.1.0", "tslib@^2.4.0", "tslib@^2.5.0", "tslib@^2.6.0":
   "integrity" "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
   "resolved" "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz"
   "version" "2.6.2"
-- 
GitLab