From dff2430f75a07d4e451223e72b8fe15b8fcc89ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Ko=C5=82odzi=C5=84ski?= Date: Tue, 18 Aug 2026 19:07:15 +0200 Subject: [PATCH 1/4] chore: linting fixes --- frontend/.eslintrc.json | 8 +- frontend/package-lock.json | 1490 +---------------- frontend/package.json | 2 +- .../Categories/CategoriesContext.jsx | 5 + .../components/FiltersForm/FiltersForm.jsx | 4 +- .../components/FiltersForm/FiltersTooltip.jsx | 56 +- frontend/src/components/Map/Map.jsx | 17 +- frontend/src/components/Map/MapComponent.jsx | 28 +- .../Map/components/AccessibilityTable.jsx | 69 +- .../Map/components/GoToLocation.jsx | 4 +- .../components/Map/components/ListView.jsx | 5 + .../Map/components/LocationControl.jsx | 2 +- .../components/LocationPermissionBanner.jsx | 4 +- .../Map/components/MapLoadingOverlay.jsx | 4 +- .../src/components/Map/components/Markers.jsx | 10 +- .../Map/components/SaveMapConfiguration.jsx | 3 +- .../Map/components/SuggestNewPointButton.jsx | 6 +- .../Map/components/SuggestNewPointDialog.jsx | 60 +- .../components/Map/components/ZoomControl.jsx | 4 +- .../components/MarkerPopup/ClusterMarker.jsx | 34 +- .../components/MarkerPopup/DesktopPopup.jsx | 11 +- .../MarkerPopup/LocationDetails.jsx | 19 +- .../components/MarkerPopup/MarkerPopup.jsx | 17 +- .../components/MarkerPopup/MobilePopup.jsx | 34 +- .../MarkerPopup/ReportProblemForm.jsx | 4 +- frontend/src/components/common/AppToaster.jsx | 4 +- .../src/components/common/Autocomplete.jsx | 9 +- frontend/src/index.js | 2 +- frontend/tests/CategoriesContext.test.jsx | 4 +- frontend/tests/FiltersForm.test.jsx | 2 +- frontend/tests/Map/MapComponent.test.jsx | 6 +- .../Map/components/SuggestNewPoint.test.jsx | 9 +- frontend/tests/MarkerPopup/CTA.test.jsx | 2 +- .../tests/MarkerPopup/ClusterMarker.test.jsx | 2 +- .../MarkerPopup/LocationDetailsBox.test.jsx | 2 +- .../MarkerClusterGroupIntegration.test.jsx | 2 +- .../tests/MarkerPopup/MarkerPopup.test.jsx | 2 +- .../MarkerPopup/ReportProblemForm.test.jsx | 2 +- 38 files changed, 250 insertions(+), 1698 deletions(-) diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json index 0df797a3..63736f23 100644 --- a/frontend/.eslintrc.json +++ b/frontend/.eslintrc.json @@ -4,8 +4,7 @@ "plugin:react/recommended", "plugin:prettier/recommended", "airbnb", - "prettier", - "wikimedia/client/es6"], + "prettier"], "plugins": ["react", "prettier", "eslint-plugin-react", "eslint-plugin-react-hooks"], "parser": "@babel/eslint-parser", "parserOptions": { @@ -34,11 +33,8 @@ "arrow-parens": "off", "class-methods-use-this": "error", "import/prefer-default-export": "error", - "react/require-default-props": "error", + "react/require-default-props": ["error", { "functions": "defaultArguments" }], "comma-dangle": "off", - "es-x/no-rest-spread-properties": "off", - "es-x/no-trailing-function-commas": "off", - "es-x/no-global-this": "off", "indent": ["error", 4], "implicit-arrow-linebreak": "off", "react/function-component-definition": [ diff --git a/frontend/package-lock.json b/frontend/package-lock.json index bc66b9a1..5e22d659 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -13,6 +13,7 @@ "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.14.6", "@mui/material": "^5.14.6", + "@react-leaflet/core": "^2.1.0", "axios": "^1.7.6", "browser-image-compression": "^2.0.2", "i18next": "^23.15.1", @@ -43,7 +44,6 @@ "eslint": "^8.30.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-prettier": "^8.10.0", - "eslint-config-wikimedia": "^0.28.2", "eslint-plugin-import": "^2.27.5", "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-prettier": "^4.2.1", @@ -2273,38 +2273,6 @@ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", "license": "MIT" }, - "node_modules/@es-joy/jsdoccomment": { - "version": "0.43.1", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.43.1.tgz", - "integrity": "sha512-I238eDtOolvCuvtxrnqtlBaw0BwdQuYqK7eA6XIonicMdOOOb75mqdIzkGDUbS04+1Di007rgm9snFRNeVrOog==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint": "^8.56.5", - "@types/estree": "^1.0.5", - "@typescript-eslint/types": "^7.2.0", - "comment-parser": "1.4.1", - "esquery": "^1.5.0", - "jsdoc-type-pratt-parser": "~4.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@es-joy/jsdoccomment/node_modules/@typescript-eslint/types": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", - "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -3385,13 +3353,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@mdn/browser-compat-data": { - "version": "5.7.6", - "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.7.6.tgz", - "integrity": "sha512-7xdrMX0Wk7grrTZQwAoy1GkvPMFoizStUoL+VmtUkAxegbCCec+3FKwOM6yc/uGU5+BEczQHXAlWiqvM8JeENg==", - "dev": true, - "license": "CC0-1.0" - }, "node_modules/@mui/core-downloads-tracker": { "version": "5.18.0", "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.18.0.tgz", @@ -4151,17 +4112,6 @@ "@types/node": "*" } }, - "node_modules/@types/eslint": { - "version": "8.56.12", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz", - "integrity": "sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -4322,13 +4272,6 @@ "undici-types": "~8.3.0" } }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", - "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -4485,212 +4428,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.0.tgz", - "integrity": "sha512-wexnCqiTg7BOGtbLDftYpRWlmLq4xfoMd7BKFR6Y75sZS3QmRKLdN3yWLhmIYgqMmP/OXWpj3H8odkb5nGURCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.62.0", - "@typescript-eslint/types": "^8.62.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.0.tgz", - "integrity": "sha512-1lX38kNxXIRb8mEc3lbq5mdHq1Pf2+U0nFU65KfT18mtPxxl0fvjuEE92mHuXPuCtElJhOrddOpyMlM3Z0umEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/visitor-keys": "8.62.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.0.tgz", - "integrity": "sha512-y2GAdB6ykaXUvuspbYnizQc4oDDz0Tz/Yc7iWrXf9mx8vm/L/0vLHCe0tS2boG96Zy+DivnVDQ9ZUEWoHqqx1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.0.tgz", - "integrity": "sha512-KvAclkktORPvM54TgLgA4z9HIV1M8zOgw9ZVNXl9f/8dLYfXYX1wkMXP7qmabpijQRV5bHJLOmoyGQbLMaUYeg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.0.tgz", - "integrity": "sha512-+hVbNxtW64pIcZWDPGbyaKF7vp2IBTVY5ma1blwwksrjdsbdqqEKvJWMGbBofei4F6Dovx1M0RJgoFeNu2279A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.62.0", - "@typescript-eslint/tsconfig-utils": "8.62.0", - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/visitor-keys": "8.62.0", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.0.tgz", - "integrity": "sha512-82r66fi9zYwZ+mTq3vKgwjbZ1PVk/DJzrXFLpG6RnBbdvH8TEGVHIs9H4d2drhkOzf0syZuD/OZvvlu6GDbP4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.62.0", - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/typescript-estree": "8.62.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.0.tgz", - "integrity": "sha512-CY3uyFSRbcQv3nnSv8S0+lDftMVz6P963PoRlxrV7ew/Md564g9ut60PYzdLM5qW4jFn93GBF+Soi90ISAN+GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.62.0", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@ungap/structured-clone": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.2.tgz", @@ -5422,16 +5159,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/are-docs-informative": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", - "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -5631,16 +5358,6 @@ "node": ">=12.0.0" } }, - "node_modules/ast-metadata-inferer": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/ast-metadata-inferer/-/ast-metadata-inferer-0.8.1.tgz", - "integrity": "sha512-ht3Dm6Zr7SXv6t1Ra6gFo0+kLDglHGrEbYihTkcycrbHw7WCcuhBzPlJYHEsIpycaUwzsJHje+vUcxXUX4ztTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@mdn/browser-compat-data": "^5.6.19" - } - }, "node_modules/ast-types-flow": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", @@ -5991,13 +5708,6 @@ "multicast-dns": "^7.2.5" } }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true, - "license": "ISC" - }, "node_modules/brace-expansion": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", @@ -6065,13 +5775,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/browserslist-config-wikimedia": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/browserslist-config-wikimedia/-/browserslist-config-wikimedia-0.7.0.tgz", - "integrity": "sha512-CTa0lv78dXKEgrYsOLCkqO+9UUS3CV9MWEOYHcymgEvx4mYxB80sCoKRCR7wW2SOMNxjaP9hohrZripjnKuRTA==", - "dev": true, - "license": "MIT" - }, "node_modules/bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -6089,19 +5792,6 @@ "dev": true, "license": "MIT" }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/bundle-name": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", @@ -6334,29 +6024,6 @@ "dev": true, "license": "MIT" }, - "node_modules/clean-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz", - "integrity": "sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/clean-regexp/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -6500,16 +6167,6 @@ "dev": true, "license": "MIT" }, - "node_modules/comment-parser": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", - "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12.0.0" - } - }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -7065,16 +6722,6 @@ "dev": true, "license": "MIT" }, - "node_modules/diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, "node_modules/dns-packet": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", @@ -7529,35 +7176,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-compat-utils": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", - "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "eslint": ">=6.0.0" - } - }, - "node_modules/eslint-compat-utils/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/eslint-config-airbnb": { "version": "19.0.4", "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", @@ -7613,32 +7231,6 @@ "eslint": ">=7.0.0" } }, - "node_modules/eslint-config-wikimedia": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.28.2.tgz", - "integrity": "sha512-5+rdnT7wH1gpKAO6tHYThg78eMhZMruJzvqku3Y5iaEY/A7kSKLFpA/vOj/snys9fKjDHC9BXmArQh+agkOoJQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist-config-wikimedia": "^0.7.0", - "eslint": "^8.57.0", - "eslint-plugin-compat": "^4.2.0", - "eslint-plugin-es-x": "^7.6.0", - "eslint-plugin-jest": "^28.5.0", - "eslint-plugin-jsdoc": "48.2.5", - "eslint-plugin-json-es": "^1.6.0", - "eslint-plugin-mediawiki": "^0.7.0", - "eslint-plugin-mocha": "^10.4.3", - "eslint-plugin-n": "^17.7.0", - "eslint-plugin-no-jquery": "^3.0.1", - "eslint-plugin-qunit": "^8.1.1", - "eslint-plugin-security": "^1.7.1", - "eslint-plugin-unicorn": "^53.0.0", - "eslint-plugin-vue": "^9.26.0", - "eslint-plugin-wdio": "^8.24.12", - "eslint-plugin-yml": "^1.14.0" - } - }, "node_modules/eslint-import-resolver-node": { "version": "0.3.10", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", @@ -7713,116 +7305,10 @@ "ms": "^2.1.1" } }, - "node_modules/eslint-plugin-compat": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-4.2.0.tgz", - "integrity": "sha512-RDKSYD0maWy5r7zb5cWQS+uSPc26mgOzdORJ8hxILmWM7S/Ncwky7BcAtXVY5iRbKjBdHsWU8Yg7hfoZjtkv7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@mdn/browser-compat-data": "^5.3.13", - "ast-metadata-inferer": "^0.8.0", - "browserslist": "^4.21.10", - "caniuse-lite": "^1.0.30001524", - "find-up": "^5.0.0", - "lodash.memoize": "^4.1.2", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=14.x" - }, - "peerDependencies": { - "eslint": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/eslint-plugin-compat/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint-plugin-compat/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint-plugin-compat/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint-plugin-compat/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-plugin-es-x": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", - "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/ota-meshi", - "https://opencollective.com/eslint" - ], - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.1.2", - "@eslint-community/regexpp": "^4.11.0", - "eslint-compat-utils": "^0.5.1" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": ">=8" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.32.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", - "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", "dependencies": { @@ -7876,96 +7362,6 @@ "node": ">=0.10.0" } }, - "node_modules/eslint-plugin-jest": { - "version": "28.14.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.14.0.tgz", - "integrity": "sha512-P9s/qXSMTpRTerE2FQ0qJet2gKbcGyFTPAJipoKxmWqR6uuFqIqk8FuEfg5yBieOezVrEfAMZrEwJ6yEp+1MFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/utils": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "engines": { - "node": "^16.10.0 || ^18.12.0 || >=20.0.0" - }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^6.0.0 || ^7.0.0 || ^8.0.0", - "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0", - "jest": "*" - }, - "peerDependenciesMeta": { - "@typescript-eslint/eslint-plugin": { - "optional": true - }, - "jest": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-jsdoc": { - "version": "48.2.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.2.5.tgz", - "integrity": "sha512-ZeTfKV474W1N9niWfawpwsXGu+ZoMXu4417eBROX31d7ZuOk8zyG66SO77DpJ2+A9Wa2scw/jRqBPnnQo7VbcQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@es-joy/jsdoccomment": "~0.43.0", - "are-docs-informative": "^0.0.2", - "comment-parser": "1.4.1", - "debug": "^4.3.4", - "escape-string-regexp": "^4.0.0", - "esquery": "^1.5.0", - "is-builtin-module": "^3.2.1", - "semver": "^7.6.1", - "spdx-expression-parse": "^4.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" - } - }, - "node_modules/eslint-plugin-jsdoc/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-plugin-json-es": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-json-es/-/eslint-plugin-json-es-1.6.0.tgz", - "integrity": "sha512-xVn6hufGQH1Aa+yqOhQ43Cq28GuitTcMpQh+uaUh27U2qnVLBrvkN+2xQSnv6zpdLEPS35JCNhq4kvhR+PQCgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.1" - }, - "peerDependencies": { - "eslint": ">= 7" - } - }, - "node_modules/eslint-plugin-json-es/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/eslint-plugin-jsx-a11y": { "version": "6.10.2", "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", @@ -8006,100 +7402,6 @@ "node": ">= 0.4" } }, - "node_modules/eslint-plugin-mediawiki": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-mediawiki/-/eslint-plugin-mediawiki-0.7.1.tgz", - "integrity": "sha512-C/OfySlkkjHJrZl1ieHlL8AyebgrppyX49DUYEVXk7n0qu7dD/vuOnzeQz28H2MkUVj3GnoFSUxqbczG6IUwwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "upath": "^2.0.1" - }, - "peerDependencies": { - "eslint": ">=5.0.0" - } - }, - "node_modules/eslint-plugin-mocha": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.5.0.tgz", - "integrity": "sha512-F2ALmQVPT1GoP27O1JTZGrV9Pqg8k79OeIuvw63UxMtQKREZtmkK1NFgkZQ2TW7L2JSSFKHFPTtHu5z8R9QNRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-utils": "^3.0.0", - "globals": "^13.24.0", - "rambda": "^7.4.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-n": { - "version": "17.24.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.24.0.tgz", - "integrity": "sha512-/gC7/KAYmfNnPNOb3eu8vw+TdVnV0zhdQwexsw6FLXbhzroVj20vRn2qL8lDWDGnAQ2J8DhdfvXxX9EoxvERvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.5.0", - "enhanced-resolve": "^5.17.1", - "eslint-plugin-es-x": "^7.8.0", - "get-tsconfig": "^4.8.1", - "globals": "^15.11.0", - "globrex": "^0.1.2", - "ignore": "^5.3.2", - "semver": "^7.6.3", - "ts-declaration-location": "^1.0.6" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": ">=8.23.0" - } - }, - "node_modules/eslint-plugin-n/node_modules/globals": { - "version": "15.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", - "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint-plugin-n/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-plugin-no-jquery": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-3.1.1.tgz", - "integrity": "sha512-LTLO3jH/Tjr1pmxCEqtV6qmt+OChv8La4fwgG470JRpgxyFF4NOzoC9CRy92GIWD3Yjl0qLEgPmD2FLQWcNEjg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "eslint": ">=8.0.0" - } - }, "node_modules/eslint-plugin-prettier": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.5.tgz", @@ -8122,23 +7424,6 @@ } } }, - "node_modules/eslint-plugin-qunit": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/eslint-plugin-qunit/-/eslint-plugin-qunit-8.2.6.tgz", - "integrity": "sha512-S1jC/DIW9J8VtNX4uG1vlf5FZVrfQFlcuiYmvTHR2IICUhubHqpWA5o+qS1tujh+81Gs39omKV2D4OXfbSJE5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "requireindex": "^1.2.0" - }, - "engines": { - "node": "^16.0.0 || ^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "eslint": ">=8.38.0" - } - }, "node_modules/eslint-plugin-react": { "version": "7.37.5", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", @@ -8222,230 +7507,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-security": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-1.7.1.tgz", - "integrity": "sha512-sMStceig8AFglhhT2LqlU5r+/fn9OwsA72O5bBuQVTssPCdQAOQzL+oMn/ZcpeUY6KcNfLJArgcrsSULNjYYdQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "safe-regex": "^2.1.1" - } - }, - "node_modules/eslint-plugin-unicorn": { - "version": "53.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-53.0.0.tgz", - "integrity": "sha512-kuTcNo9IwwUCfyHGwQFOK/HjJAYzbODHN3wP0PgqbW+jbXqpNWxNVpVhj2tO9SixBwuAdmal8rVcWKBxwFnGuw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.24.5", - "@eslint-community/eslint-utils": "^4.4.0", - "@eslint/eslintrc": "^3.0.2", - "ci-info": "^4.0.0", - "clean-regexp": "^1.0.0", - "core-js-compat": "^3.37.0", - "esquery": "^1.5.0", - "indent-string": "^4.0.0", - "is-builtin-module": "^3.2.1", - "jsesc": "^3.0.2", - "pluralize": "^8.0.0", - "read-pkg-up": "^7.0.1", - "regexp-tree": "^0.1.27", - "regjsparser": "^0.10.0", - "semver": "^7.6.1", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=18.18" - }, - "funding": { - "url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1" - }, - "peerDependencies": { - "eslint": ">=8.56.0" - } - }, - "node_modules/eslint-plugin-unicorn/node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.14.0", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.5", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-plugin-unicorn/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-plugin-unicorn/node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-plugin-unicorn/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint-plugin-unicorn/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-plugin-vue": { - "version": "9.33.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.33.0.tgz", - "integrity": "sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "globals": "^13.24.0", - "natural-compare": "^1.4.0", - "nth-check": "^2.1.1", - "postcss-selector-parser": "^6.0.15", - "semver": "^7.6.3", - "vue-eslint-parser": "^9.4.3", - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": "^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" - } - }, - "node_modules/eslint-plugin-vue/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-plugin-wdio": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-wdio/-/eslint-plugin-wdio-8.37.0.tgz", - "integrity": "sha512-X217zXxSqj1IPWu3bxN7D/xEUmNk7Jg5lBf2JwYH3mCogaqL2tnHZnwt0EQ5D9oEejfEl2+4zqHSzhXq1X7F2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^16.13 || >=18" - } - }, - "node_modules/eslint-plugin-yml": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-yml/-/eslint-plugin-yml-1.19.1.tgz", - "integrity": "sha512-bYkOxyEiXh9WxUhVYPELdSHxGG5pOjCSeJOVkfdIyj6tuiHDxrES2WAW1dBxn3iaZQey57XflwLtCYRcNPOiOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.2", - "diff-sequences": "^27.5.1", - "escape-string-regexp": "4.0.0", - "eslint-compat-utils": "^0.6.0", - "natural-compare": "^1.4.0", - "yaml-eslint-parser": "^1.2.1" - }, - "engines": { - "node": "^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ota-meshi" - }, - "peerDependencies": { - "eslint": ">=6.0.0" - } - }, - "node_modules/eslint-plugin-yml/node_modules/eslint-compat-utils": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.6.5.tgz", - "integrity": "sha512-vAUHYzue4YAa2hNACjB8HvUQj5yehAZgiClyFVVom9cP8z5NSFq3PwB/TtJslN2zAMgRX6FCFCjYBbQh71g5RQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "eslint": ">=6.0.0" - } - }, - "node_modules/eslint-plugin-yml/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -8470,25 +7531,6 @@ "node": ">=4.0" } }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, "node_modules/eslint-visitor-keys": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", @@ -8883,24 +7925,6 @@ "bser": "2.1.1" } }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -9295,19 +8319,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-tsconfig": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", - "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, "node_modules/glob": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", @@ -9419,13 +8430,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globrex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", - "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", - "dev": true, - "license": "MIT" - }, "node_modules/goober": { "version": "2.1.19", "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.19.tgz", @@ -9574,13 +8578,6 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true, - "license": "ISC" - }, "node_modules/hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", @@ -10063,22 +9060,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", - "dev": true, - "license": "MIT", - "dependencies": { - "builtin-modules": "^3.3.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -11645,16 +10626,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsdoc-type-pratt-parser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", - "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/jsdom": { "version": "26.1.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", @@ -11949,13 +10920,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -12483,29 +11447,6 @@ "node": ">=18" } }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -12526,20 +11467,7 @@ "path-key": "^3.0.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" + "node": ">=8" } }, "node_modules/nwsapi": { @@ -13051,16 +11979,6 @@ "node": ">=16.0.0" } }, - "node_modules/pluralize": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", - "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -13191,20 +12109,6 @@ "postcss": "^8.1.0" } }, - "node_modules/postcss-selector-parser": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", - "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", @@ -13430,13 +12334,6 @@ ], "license": "MIT" }, - "node_modules/rambda": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/rambda/-/rambda-7.5.0.tgz", - "integrity": "sha512-y/M9weqWAH4iopRd7EHDEQQvpFPHj1AA3oHozE9tfITHUtTR7Z9PSlIRRG2l1GuW7sefC1cXFfIcF+cgnShdBA==", - "dev": true, - "license": "MIT" - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -13625,60 +12522,6 @@ "react-dom": ">=16.6.0" } }, - "node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -13797,16 +12640,6 @@ "node": ">=4" } }, - "node_modules/regexp-tree": { - "version": "0.1.27", - "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", - "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", - "dev": true, - "license": "MIT", - "bin": { - "regexp-tree": "bin/regexp-tree" - } - }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -13866,28 +12699,6 @@ "dev": true, "license": "MIT" }, - "node_modules/regjsparser": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.10.0.tgz", - "integrity": "sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -13908,16 +12719,6 @@ "node": ">=0.10.0" } }, - "node_modules/requireindex": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", - "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.5" - } - }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -13969,16 +12770,6 @@ "node": ">=8" } }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -14141,16 +12932,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safe-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz", - "integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "regexp-tree": "~0.1.1" - } - }, "node_modules/safe-regex-test": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", @@ -14639,53 +13420,6 @@ "node": ">=0.10.0" } }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-correct/node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true, - "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", - "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.23", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", - "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", - "dev": true, - "license": "CC0-1.0" - }, "node_modules/spdy": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", @@ -15430,23 +14164,6 @@ "dev": true, "license": "MIT" }, - "node_modules/tinyglobby": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, "node_modules/tldts": { "version": "6.1.86", "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", @@ -15540,42 +14257,6 @@ "tslib": "2" } }, - "node_modules/ts-api-utils": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", - "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/ts-declaration-location": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/ts-declaration-location/-/ts-declaration-location-1.0.7.tgz", - "integrity": "sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==", - "dev": true, - "funding": [ - { - "type": "ko-fi", - "url": "https://ko-fi.com/rebeccastevens" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/ts-declaration-location" - } - ], - "license": "BSD-3-Clause", - "dependencies": { - "picomatch": "^4.0.2" - }, - "peerDependencies": { - "typescript": ">=4.0.0" - } - }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -15771,8 +14452,8 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, "license": "Apache-2.0", + "optional": true, "peer": true, "bin": { "tsc": "bin/tsc", @@ -15926,17 +14607,6 @@ "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" } }, - "node_modules/upath": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", - "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4", - "yarn": "*" - } - }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -16077,28 +14747,6 @@ "node": ">=10.12.0" } }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -16118,74 +14766,6 @@ "node": ">=0.10.0" } }, - "node_modules/vue-eslint-parser": { - "version": "9.4.3", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", - "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.1", - "esquery": "^1.4.0", - "lodash": "^4.17.21", - "semver": "^7.3.6" - }, - "engines": { - "node": "^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=6.0.0" - } - }, - "node_modules/vue-eslint-parser/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/vue-eslint-parser/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -17027,16 +15607,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/xml-name-validator": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", - "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12" - } - }, "node_modules/xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", @@ -17070,52 +15640,6 @@ "node": ">= 6" } }, - "node_modules/yaml-eslint-parser": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/yaml-eslint-parser/-/yaml-eslint-parser-1.3.2.tgz", - "integrity": "sha512-odxVsHAkZYYglR30aPYRY4nUGJnoJ2y1ww2HDvZALo0BDETv9kWbi16J52eHs+PWRNmF4ub6nZqfVOeesOvntg==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.0.0", - "yaml": "^2.0.0" - }, - "engines": { - "node": "^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ota-meshi" - } - }, - "node_modules/yaml-eslint-parser/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/yaml-eslint-parser/node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, "node_modules/yargs": { "version": "17.7.3", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index b9a7bce5..3635135f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -42,7 +42,6 @@ "eslint": "^8.30.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-prettier": "^8.10.0", - "eslint-config-wikimedia": "^0.28.2", "eslint-plugin-import": "^2.27.5", "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-prettier": "^4.2.1", @@ -64,6 +63,7 @@ "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.14.6", "@mui/material": "^5.14.6", + "@react-leaflet/core": "^2.1.0", "axios": "^1.7.6", "browser-image-compression": "^2.0.2", "i18next": "^23.15.1", diff --git a/frontend/src/components/Categories/CategoriesContext.jsx b/frontend/src/components/Categories/CategoriesContext.jsx index c188869a..8ec5f2be 100644 --- a/frontend/src/components/Categories/CategoriesContext.jsx +++ b/frontend/src/components/Categories/CategoriesContext.jsx @@ -1,4 +1,5 @@ import React, { useState, useContext, createContext, useMemo, useEffect, useCallback } from 'react'; +import PropTypes from 'prop-types'; import { httpService } from '../../services/http/httpService'; /** @@ -67,6 +68,10 @@ export const CategoriesProvider = ({ children }) => { return {children}; }; +CategoriesProvider.propTypes = { + children: PropTypes.node.isRequired, +}; + /** * Custom hook to access categories context. * Must be used within a CategoriesProvider component. diff --git a/frontend/src/components/FiltersForm/FiltersForm.jsx b/frontend/src/components/FiltersForm/FiltersForm.jsx index 603135d8..3b9ab257 100644 --- a/frontend/src/components/FiltersForm/FiltersForm.jsx +++ b/frontend/src/components/FiltersForm/FiltersForm.jsx @@ -239,7 +239,7 @@ const LoadingSkeleton = () => ( ); -export const FiltersForm = () => { +const FiltersForm = () => { const { t } = useTranslation(); const { categories: selectedFilters, @@ -456,3 +456,5 @@ export const FiltersForm = () => { ); }; + +export default FiltersForm; diff --git a/frontend/src/components/FiltersForm/FiltersTooltip.jsx b/frontend/src/components/FiltersForm/FiltersTooltip.jsx index dd5b34bf..1081115e 100644 --- a/frontend/src/components/FiltersForm/FiltersTooltip.jsx +++ b/frontend/src/components/FiltersForm/FiltersTooltip.jsx @@ -27,37 +27,35 @@ const IconWrapper = styled.span` * @param {string} props.text - The help text to display in the tooltip * @returns {React.ReactElement} Info icon with attached MUI tooltip */ -const FiltersTooltip = ({ text }) => { - return ( - ( + - - - - - ); -}; + }, + }} + > + + + + +); FiltersTooltip.propTypes = { text: PropTypes.string.isRequired, diff --git a/frontend/src/components/Map/Map.jsx b/frontend/src/components/Map/Map.jsx index 045f7e19..ba009829 100644 --- a/frontend/src/components/Map/Map.jsx +++ b/frontend/src/components/Map/Map.jsx @@ -1,13 +1,10 @@ import ReactDOM from 'react-dom/client'; -import React, { useEffect } from 'react'; -import { httpService } from '../../services/http/httpService'; -import { FiltersForm } from '../FiltersForm/FiltersForm'; -import { MapComponent } from './MapComponent'; -import { useMapStore } from './store/map.store'; -import { CategoriesProvider } from '../Categories/CategoriesContext'; +import React from 'react'; import { createPortal } from 'react-dom'; -import { AppToaster } from '../common/AppToaster'; -import useDebounce from '../../utils/hooks/useDebounce'; +import FiltersForm from '../FiltersForm/FiltersForm'; +import MapComponent from './MapComponent'; +import { CategoriesProvider } from '../Categories/CategoriesContext'; +import AppToaster from '../common/AppToaster'; /** * Wrapper component that renders the map and filters form into their respective DOM placeholders. @@ -41,10 +38,12 @@ const MapWrap = () => { * * @returns {void} */ -export const MapContainer = () => { +const MapContainer = () => { const appContainer = document.createElement('div'); document.body.appendChild(appContainer); const root = ReactDOM.createRoot(appContainer); root.render(); }; + +export default MapContainer; diff --git a/frontend/src/components/Map/MapComponent.jsx b/frontend/src/components/Map/MapComponent.jsx index 137efac6..1271ddba 100644 --- a/frontend/src/components/Map/MapComponent.jsx +++ b/frontend/src/components/Map/MapComponent.jsx @@ -1,19 +1,19 @@ import React, { useState } from 'react'; import { MapContainer, TileLayer } from 'react-leaflet'; import Control from 'react-leaflet-custom-control'; -import { LocationControl } from './components/LocationControl'; -import { SuggestNewPointButton } from './components/SuggestNewPointButton'; -import { LocationPermissionBanner } from './components/LocationPermissionBanner'; +import LocationControl from './components/LocationControl'; +import SuggestNewPointButton from './components/SuggestNewPointButton'; +import LocationPermissionBanner from './components/LocationPermissionBanner'; import { mapConfig } from './map.config'; -import { CustomZoomControl } from './components/ZoomControl'; +import CustomZoomControl from './components/ZoomControl'; import MapAutocomplete from './components/MapAutocomplete'; import ListViewButton from './components/ListView'; import AccessibilityTable from './components/AccessibilityTable'; import SaveMapConfiguration from './components/SaveMapConfiguration'; -import { Markers } from './components/Markers'; -import { MapLoadingOverlay } from './components/MapLoadingOverlay'; +import Markers from './components/Markers'; +import MapLoadingOverlay from './components/MapLoadingOverlay'; import { LocationProvider, useLocation } from './context/LocationContext'; -import { GoToLocation } from './components/GoToLocation'; +import GoToLocation from './components/GoToLocation'; import MapOverlays from '../../plugins/MapOverlays'; /** @@ -83,10 +83,10 @@ const MapComponentInner = () => { * * @returns {React.ReactElement} MapContainer with markers and controls, or AccessibilityTable when list view is active */ -export const MapComponent = () => { - return ( - - - - ); -}; +const MapComponent = () => ( + + + +); + +export default MapComponent; diff --git a/frontend/src/components/Map/components/AccessibilityTable.jsx b/frontend/src/components/Map/components/AccessibilityTable.jsx index a375169d..966d909a 100644 --- a/frontend/src/components/Map/components/AccessibilityTable.jsx +++ b/frontend/src/components/Map/components/AccessibilityTable.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import Table from '@mui/material/Table'; import TableBody from '@mui/material/TableBody'; @@ -44,61 +44,38 @@ const AccessibilityTable = ({ userPosition, setIsAccessibilityTableOpen }) => { }, [categories, userPosition]); useEffect(() => { + if (!data) { + return; + } try { - const uniqueHeadersSet = new Set(); - if (!data) { - return; - } - uniqueHeadersSet.add(t('title')); - for (const place of data) { - for (const item of place.data) { + const uniqueHeadersSet = new Set([t('title')]); + data.forEach(place => { + place.data.forEach(item => { uniqueHeadersSet.add(item[0]); - } - } - const uniqueNumberedKeys = {}; - for (const [index, key] of Array.from(uniqueHeadersSet).entries()) { - uniqueNumberedKeys[key] = index; - } - const orderedKeysArray = Object.keys(uniqueNumberedKeys).sort( - (a, b) => uniqueNumberedKeys[a] - uniqueNumberedKeys[b], - ); + }); + }); + const orderedKeysArray = Array.from(uniqueHeadersSet); setHeaders(orderedKeysArray); - const rowsLocal = []; - const getArr = (placeItem, key) => { const item = placeItem.find(it => it[0] === key); - if (!item) { - return ['', '—']; - } - return item; + return item || ['', '—']; }; - for (const it of data) { - const row = []; - const place = it.data; - row.push(it.title); + const rowsLocal = data.map(it => { + const row = [it.title]; // Skip first element (title) and iterate over remaining keys - for (const key of orderedKeysArray.slice(1)) { - const values = getArr(place, key); - if (values === undefined) { - continue; - } - const value = values[1]; - if (Array.isArray(value)) { - const str = value.join(', '); - row.push(str); - continue; - } - row.push(value); - } - rowsLocal.push(row); - } + orderedKeysArray.slice(1).forEach(key => { + const [, value] = getArr(it.data, key); + row.push(Array.isArray(value) ? value.join(', ') : value); + }); + return row; + }); setRows(rowsLocal); } catch (error) { console.log('AccessibilityTable: ', error); } - }, [data]); + }, [data, t]); return ( <> @@ -125,13 +102,13 @@ const AccessibilityTable = ({ userPosition, setIsAccessibilityTableOpen }) => { - {rows.map((row, index) => ( + {rows.map(row => ( - {row.map((cell, index) => ( - + {row.map((cell, cellIndex) => ( + {cell.type ? : cell} ))} diff --git a/frontend/src/components/Map/components/GoToLocation.jsx b/frontend/src/components/Map/components/GoToLocation.jsx index 57441ef5..7ee0ac04 100644 --- a/frontend/src/components/Map/components/GoToLocation.jsx +++ b/frontend/src/components/Map/components/GoToLocation.jsx @@ -10,7 +10,7 @@ import { useMapStore } from '../store/map.store'; * * @returns {null} This component renders nothing */ -export const GoToLocation = () => { +const GoToLocation = () => { const map = useMap(); const [hasNavigated, setHasNavigated] = useState(false); const setSelectedLocationId = useMapStore(state => state.setSelectedLocationId); @@ -50,3 +50,5 @@ export const GoToLocation = () => { return null; }; + +export default GoToLocation; diff --git a/frontend/src/components/Map/components/ListView.jsx b/frontend/src/components/Map/components/ListView.jsx index 799ff2ae..bbfb6e57 100644 --- a/frontend/src/components/Map/components/ListView.jsx +++ b/frontend/src/components/Map/components/ListView.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; import { Button, Tooltip } from '@mui/material'; import ViewListIcon from '@mui/icons-material/ViewList'; @@ -65,4 +66,8 @@ const Wrapper = styled.div` z-index: 9999999; `; +ListView.propTypes = { + onClick: PropTypes.func.isRequired, +}; + export default ListView; diff --git a/frontend/src/components/Map/components/LocationControl.jsx b/frontend/src/components/Map/components/LocationControl.jsx index 88b45bb8..62e6f50d 100644 --- a/frontend/src/components/Map/components/LocationControl.jsx +++ b/frontend/src/components/Map/components/LocationControl.jsx @@ -86,4 +86,4 @@ const LocationControl = () => { ); }; -export { LocationControl }; +export default LocationControl; diff --git a/frontend/src/components/Map/components/LocationPermissionBanner.jsx b/frontend/src/components/Map/components/LocationPermissionBanner.jsx index 29a6238f..9d27f01c 100644 --- a/frontend/src/components/Map/components/LocationPermissionBanner.jsx +++ b/frontend/src/components/Map/components/LocationPermissionBanner.jsx @@ -12,7 +12,7 @@ const BANNER_DISMISSED_KEY = 'goodmap_location_banner_dismissed'; * * @returns {React.ReactElement|null} Banner component or null if not applicable */ -export const LocationPermissionBanner = () => { +const LocationPermissionBanner = () => { const { t } = useTranslation(); const { permissionState, requestGeolocation } = useLocation(); const [dismissed, setDismissed] = useState(true); @@ -52,6 +52,8 @@ export const LocationPermissionBanner = () => { ); }; +export default LocationPermissionBanner; + const BannerContainer = styled.div` position: absolute; /* Positioned above the zoom controls and other map buttons */ diff --git a/frontend/src/components/Map/components/MapLoadingOverlay.jsx b/frontend/src/components/Map/components/MapLoadingOverlay.jsx index fe7c21be..d882f4eb 100644 --- a/frontend/src/components/Map/components/MapLoadingOverlay.jsx +++ b/frontend/src/components/Map/components/MapLoadingOverlay.jsx @@ -56,7 +56,7 @@ const LoadingText = styled.p` * @param {string} [props.text] - Optional loading text to display * @returns {React.ReactElement} Loading overlay with spinner or custom GIF */ -export const MapLoadingOverlay = ({ isLoading, text = null }) => { +const MapLoadingOverlay = ({ isLoading, text = null }) => { const customGif = globalThis.MAP_LOADING_GIF; return ( @@ -71,3 +71,5 @@ MapLoadingOverlay.propTypes = { isLoading: PropTypes.bool.isRequired, text: PropTypes.string, }; + +export default MapLoadingOverlay; diff --git a/frontend/src/components/Map/components/Markers.jsx b/frontend/src/components/Map/components/Markers.jsx index 82a83024..27247b03 100644 --- a/frontend/src/components/Map/components/Markers.jsx +++ b/frontend/src/components/Map/components/Markers.jsx @@ -3,9 +3,9 @@ import PropTypes from 'prop-types'; import { useMap } from 'react-leaflet'; import MarkerClusterGroup from 'react-leaflet-cluster'; import { httpService } from '../../../services/http/httpService'; -import { MarkerPopup } from '../../MarkerPopup/MarkerPopup'; +import MarkerPopup from '../../MarkerPopup/MarkerPopup'; import { useCategories } from '../../Categories/CategoriesContext'; -import { ClusterMarker } from '../../MarkerPopup/ClusterMarker'; +import ClusterMarker from '../../MarkerPopup/ClusterMarker'; /** * Converts location data into marker components. @@ -43,7 +43,7 @@ const getMarkers = locations => { * @param {Function} [props.onLoadingChange] - Callback fired when loading state changes * @returns {React.ReactElement|Array} MarkerClusterGroup containing location markers, or empty array while loading */ -export const Markers = ({ onLoadingChange = null }) => { +const Markers = ({ onLoadingChange = null }) => { const { categories, isInitialized } = useCategories(); const [markers, setMarkers] = useState([]); const [areMarkersLoaded, setAreMarkersLoaded] = useState(false); @@ -74,7 +74,7 @@ export const Markers = ({ onLoadingChange = null }) => { // Only use client-side clustering when server-side clustering is disabled const markerCluster = useServerSideClustering ? ( - <>{markersToAdd} + markersToAdd ) : ( { Markers.propTypes = { onLoadingChange: PropTypes.func, }; + +export default Markers; diff --git a/frontend/src/components/Map/components/SaveMapConfiguration.jsx b/frontend/src/components/Map/components/SaveMapConfiguration.jsx index 454f907d..7e7eb608 100644 --- a/frontend/src/components/Map/components/SaveMapConfiguration.jsx +++ b/frontend/src/components/Map/components/SaveMapConfiguration.jsx @@ -15,7 +15,6 @@ const SaveMapConfiguration = () => { useEffect(() => { const updateMapState = () => { - const bounds = map.getBounds(); const zoom = map.getZoom(); useMapStore.getState().setMapConfiguration({ @@ -24,7 +23,7 @@ const SaveMapConfiguration = () => { // northWestBoundLng: bounds.getNorthWest().lng, // southEastBoundLat: bounds.getSouthEast().lat, // southEastBoundLng: bounds.getSouthEast().lng, - zoom: zoom, + zoom, }); }; diff --git a/frontend/src/components/Map/components/SuggestNewPointButton.jsx b/frontend/src/components/Map/components/SuggestNewPointButton.jsx index 80c32037..05a4f166 100644 --- a/frontend/src/components/Map/components/SuggestNewPointButton.jsx +++ b/frontend/src/components/Map/components/SuggestNewPointButton.jsx @@ -5,7 +5,7 @@ import AddIcon from '@mui/icons-material/Add'; import { useTranslation } from 'react-i18next'; import { buttonStyle, getLocationAwareStyles } from '../../../styles/buttonStyle'; import { useLocation } from '../context/LocationContext'; -import { SuggestNewPointDialog } from './SuggestNewPointDialog'; +import SuggestNewPointDialog from './SuggestNewPointDialog'; /** * Button that opens the "suggest a new point" form. Opening requires the user's @@ -14,7 +14,7 @@ import { SuggestNewPointDialog } from './SuggestNewPointDialog'; * * @returns {React.ReactElement} Button and its suggestion dialog */ -export const SuggestNewPointButton = () => { +const SuggestNewPointButton = () => { const { t } = useTranslation(); const { locationGranted, requestLocationWithFeedback } = useLocation(); const [showNewPointBox, setShowNewPointSuggestionBox] = useState(false); @@ -53,3 +53,5 @@ export const SuggestNewPointButton = () => { ); }; + +export default SuggestNewPointButton; diff --git a/frontend/src/components/Map/components/SuggestNewPointDialog.jsx b/frontend/src/components/Map/components/SuggestNewPointDialog.jsx index 2824121a..cc4c9ade 100644 --- a/frontend/src/components/Map/components/SuggestNewPointDialog.jsx +++ b/frontend/src/components/Map/components/SuggestNewPointDialog.jsx @@ -24,11 +24,11 @@ import AddAPhotoIcon from '@mui/icons-material/AddAPhoto'; import RefreshIcon from '@mui/icons-material/Refresh'; import axios from 'axios'; import { useTranslation } from 'react-i18next'; +import imageCompression from 'browser-image-compression'; import { getCsrfToken } from '../../../utils/csrf'; import { useLocation } from '../context/LocationContext'; import { useCategories } from '../../Categories/CategoriesContext'; import { toast } from '../../../utils/toast'; -import imageCompression from 'browser-image-compression'; // Map a category's options to a { key: translation } object. // Options come as [[key, translation], ...] or [key, ...]. @@ -36,7 +36,8 @@ const mapCategoryOptions = categoryOptions => { const optionMap = {}; categoryOptions.forEach(opt => { if (Array.isArray(opt)) { - optionMap[opt[0]] = opt[1]; + const [key, value] = opt; + optionMap[key] = value; } else { optionMap[opt] = opt; } @@ -113,7 +114,7 @@ const useScrollToTop = trigger => { * @param {{open: boolean, onClose: () => void}} props * @returns {React.ReactElement} Dialog with the new point suggestion form */ -export const SuggestNewPointDialog = ({ open, onClose }) => { +const SuggestNewPointDialog = ({ open, onClose }) => { const { t } = useTranslation(); const { userPosition, requestLocationWithFeedback } = useLocation(); const { categoriesData } = useCategories(); @@ -280,24 +281,19 @@ export const SuggestNewPointDialog = ({ open, onClose }) => { }; // Helper to get translated field label - const getFieldLabel = fieldName => { - // Check static translations first, then category translations, then fallback to raw name - return ( - staticFieldTranslations[fieldName] || - categoryTranslations.fieldNames[fieldName] || - fieldName - ); - }; + // Check static translations first, then category translations, then fallback to raw name + const getFieldLabel = fieldName => + staticFieldTranslations[fieldName] || + categoryTranslations.fieldNames[fieldName] || + fieldName; // Helper to get translated option label - const getOptionLabel = (fieldName, optionKey) => { - return categoryTranslations.options[fieldName]?.[optionKey] || optionKey; - }; + const getOptionLabel = (fieldName, optionKey) => + categoryTranslations.options[fieldName]?.[optionKey] || optionKey; // Helper to get translated selected values for display - const getSelectedDisplay = (fieldName, selectedValues) => { - return selectedValues.map(val => getOptionLabel(fieldName, val)).join(', '); - }; + const getSelectedDisplay = (fieldName, selectedValues) => + selectedValues.map(val => getOptionLabel(fieldName, val)).join(', '); // Render form field based on field type and whether it's a category const renderFormField = (fieldName, fieldType) => { @@ -328,7 +324,8 @@ export const SuggestNewPointDialog = ({ open, onClose }) => { ); - } else if (isCategory) { + } + if (isCategory) { // Single select for category fields return ( @@ -347,20 +344,19 @@ export const SuggestNewPointDialog = ({ open, onClose }) => { ); - } else { - // Text field for non-category fields - return ( - - ); } + // Text field for non-category fields + return ( + + ); }; return ( @@ -445,3 +441,5 @@ SuggestNewPointDialog.propTypes = { open: PropTypes.bool.isRequired, onClose: PropTypes.func.isRequired, }; + +export default SuggestNewPointDialog; diff --git a/frontend/src/components/Map/components/ZoomControl.jsx b/frontend/src/components/Map/components/ZoomControl.jsx index 07c5036f..598d9a15 100644 --- a/frontend/src/components/Map/components/ZoomControl.jsx +++ b/frontend/src/components/Map/components/ZoomControl.jsx @@ -11,7 +11,7 @@ import { zoomInButtonStyle, zoomOutButtonStyle } from '../../../styles/buttonSty * * @returns {React.ReactElement} Control component with zoom in/out buttons */ -export const CustomZoomControl = () => { +const CustomZoomControl = () => { const map = useMap(); const handleZoomIn = () => { @@ -45,3 +45,5 @@ export const CustomZoomControl = () => { ); }; + +export default CustomZoomControl; diff --git a/frontend/src/components/MarkerPopup/ClusterMarker.jsx b/frontend/src/components/MarkerPopup/ClusterMarker.jsx index bab9a8e2..d247c69a 100644 --- a/frontend/src/components/MarkerPopup/ClusterMarker.jsx +++ b/frontend/src/components/MarkerPopup/ClusterMarker.jsx @@ -16,7 +16,7 @@ import styled from 'styled-components'; * @param {number} props.cluster.cluster_count - Number of markers in this cluster * @returns {React.ReactElement} Marker component with custom cluster icon */ -export const ClusterMarker = ({ cluster }) => { +const ClusterMarker = ({ cluster }) => { const map = useMap(); const handleClusterClick = () => { const targetZoom = Math.min(map.getZoom() + 5, map.getMaxZoom()); @@ -24,14 +24,16 @@ export const ClusterMarker = ({ cluster }) => { }; // Create a DivIcon with the rendered React component (memoized to prevent recreation) - const clusterIcon = useMemo(() => { - return new DivIcon({ - html: ReactDOMServer.renderToString(), - className: 'custom-cluster-icon', - iconSize: [30, 30], - iconAnchor: [15, 15], - }); - }, [cluster.cluster_count]); + const clusterIcon = useMemo( + () => + new DivIcon({ + html: ReactDOMServer.renderToString(), + className: 'custom-cluster-icon', + iconSize: [30, 30], + iconAnchor: [15, 15], + }), + [cluster.cluster_count], + ); return ( { * @param {number} props.cluster.cluster_count - Number of markers in this cluster * @returns {React.ReactElement} Styled circular icon with cluster count */ -const ClusterMarkerIcon = ({ cluster }) => { - return ( - - {cluster.cluster_count} - - ); -}; +const ClusterMarkerIcon = ({ cluster }) => ( + + {cluster.cluster_count} + +); /** * Styled container for the cluster marker icon. @@ -97,3 +97,5 @@ ClusterMarker.propTypes = { cluster_count: PropTypes.number.isRequired, }).isRequired, }; + +export default ClusterMarker; diff --git a/frontend/src/components/MarkerPopup/DesktopPopup.jsx b/frontend/src/components/MarkerPopup/DesktopPopup.jsx index c4690924..b9628ae9 100644 --- a/frontend/src/components/MarkerPopup/DesktopPopup.jsx +++ b/frontend/src/components/MarkerPopup/DesktopPopup.jsx @@ -1,4 +1,5 @@ import React, { useEffect, useRef } from 'react'; +import PropTypes from 'prop-types'; import { Popup } from 'react-leaflet'; import styled from 'styled-components'; @@ -18,11 +19,13 @@ const StyledPopup = styled(Popup)` * @param {React.ReactNode} props.children - Content to display inside the popup * @returns {React.ReactElement} Styled Popup component that auto-opens on mount */ -export const DesktopPopup = ({ children }) => { +const DesktopPopup = ({ children }) => { const popupRef = useRef(null); useEffect(() => { if (popupRef.current) { + // Leaflet's internal Marker reference - no public API exposes this. + // eslint-disable-next-line no-underscore-dangle const marker = popupRef.current._source; if (marker) { marker.openPopup(); @@ -32,3 +35,9 @@ export const DesktopPopup = ({ children }) => { return {children}; }; + +DesktopPopup.propTypes = { + children: PropTypes.node.isRequired, +}; + +export default DesktopPopup; diff --git a/frontend/src/components/MarkerPopup/LocationDetails.jsx b/frontend/src/components/MarkerPopup/LocationDetails.jsx index e6c87d46..2fffc2cb 100644 --- a/frontend/src/components/MarkerPopup/LocationDetails.jsx +++ b/frontend/src/components/MarkerPopup/LocationDetails.jsx @@ -5,14 +5,13 @@ import ReportProblemOutlinedIcon from '@mui/icons-material/ReportProblemOutlined import ShareIcon from '@mui/icons-material/Share'; import { useTranslation } from 'react-i18next'; import { isMobile } from 'react-device-detect'; +import React, { useState } from 'react'; import { buttonStyleSmall } from '../../styles/buttonStyle'; import getContentAsString from './fieldContent'; import FieldRenderer from './FieldRenderer'; -import { ReportProblemForm } from './ReportProblemForm'; +import ReportProblemForm from './ReportProblemForm'; import { toast } from '../../utils/toast'; -import React, { useState } from 'react'; - const PopupContainer = styled.div` padding: 8px 4px; min-width: 280px; @@ -209,7 +208,7 @@ const LocationDetails = ({ place }) => { {CTACategories.length > 0 && ( - {CTACategories.map(([_category, value]) => ( + {CTACategories.map(([, value]) => ( ))} @@ -222,6 +221,8 @@ LocationDetails.propTypes = { place: PropTypes.shape({ title: PropTypes.string.isRequired, subtitle: PropTypes.string, + // [category, value] tuples where value's shape varies by field type - can't be typed more precisely. + // eslint-disable-next-line react/forbid-prop-types data: PropTypes.arrayOf(PropTypes.array).isRequired, }).isRequired, }; @@ -300,13 +301,13 @@ ShareLocationButton.propTypes = { * @param {string} props.place.metadata.uuid - Unique identifier for the location * @returns {React.ReactElement} Fragment containing location details, navigation button, and report form */ -export const LocationDetailsBox = ({ place }) => { +const LocationDetailsBox = ({ place }) => { const { t } = useTranslation(); const [showForm, setShowForm] = useState(false); const toggleForm = () => setShowForm(!showForm); return ( - + <>
{
{showForm && } -
+ ); }; @@ -345,6 +346,8 @@ LocationDetailsBox.propTypes = { place: PropTypes.shape({ title: PropTypes.string.isRequired, subtitle: PropTypes.string, + // [category, value] tuples where value's shape varies by field type - can't be typed more precisely. + // eslint-disable-next-line react/forbid-prop-types data: PropTypes.arrayOf(PropTypes.array).isRequired, position: PropTypes.arrayOf(PropTypes.number).isRequired, metadata: PropTypes.shape({ @@ -352,3 +355,5 @@ LocationDetailsBox.propTypes = { }).isRequired, }).isRequired, }; + +export default LocationDetailsBox; diff --git a/frontend/src/components/MarkerPopup/MarkerPopup.jsx b/frontend/src/components/MarkerPopup/MarkerPopup.jsx index 5c53e582..17946f42 100644 --- a/frontend/src/components/MarkerPopup/MarkerPopup.jsx +++ b/frontend/src/components/MarkerPopup/MarkerPopup.jsx @@ -7,9 +7,9 @@ import { useTranslation } from 'react-i18next'; import { httpService } from '../../services/http/httpService'; import { useMapStore } from '../Map/store/map.store'; -import { LocationDetailsBox } from './LocationDetails'; -import { MobilePopup } from './MobilePopup'; -import { DesktopPopup } from './DesktopPopup'; +import LocationDetailsBox from './LocationDetails'; +import MobilePopup from './MobilePopup'; +import DesktopPopup from './DesktopPopup'; import iconAsterisk from '../../res/img/marker-icon-asterisk.png'; /** @@ -63,6 +63,12 @@ const LocationDetailsBoxWrapper = ({ theplace }) => { return {renderContent()}; }; +LocationDetailsBoxWrapper.propTypes = { + theplace: PropTypes.shape({ + uuid: PropTypes.string.isRequired, + }).isRequired, +}; + /** * Custom Leaflet icon for markers with remarks/special annotations. * Displays an asterisk icon to visually distinguish remarked locations from standard markers. @@ -84,7 +90,7 @@ const asteriskIcon = new Icon({ * @param {boolean} [props.place.has_remark] - Whether this location has a remark (uses asterisk icon if true) * @returns {React.ReactElement} Leaflet Marker component with click-to-show-details functionality */ -export const MarkerPopup = ({ place }) => { +const MarkerPopup = ({ place }) => { const selectedLocationId = useMapStore(state => state.selectedLocationId); const setSelectedLocationId = useMapStore(state => state.setSelectedLocationId); const [isClicked, setIsClicked] = useState(false); @@ -123,6 +129,7 @@ export const MarkerPopup = ({ place }) => { } return ( + // eslint-disable-next-line react/jsx-props-no-spreading -- icon is only conditionally set {isClicked && } @@ -136,3 +143,5 @@ MarkerPopup.propTypes = { uuid: PropTypes.string.isRequired, }).isRequired, }; + +export default MarkerPopup; diff --git a/frontend/src/components/MarkerPopup/MobilePopup.jsx b/frontend/src/components/MarkerPopup/MobilePopup.jsx index 29746309..73ec7337 100644 --- a/frontend/src/components/MarkerPopup/MobilePopup.jsx +++ b/frontend/src/components/MarkerPopup/MobilePopup.jsx @@ -1,8 +1,22 @@ import React, { useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; import { Dialog, DialogContent, DialogTitle, IconButton, Slide } from '@mui/material'; import CloseIcon from '@mui/icons-material/Close'; import { useLeafletContext } from '@react-leaflet/core'; +/** + * Slide transition component for the mobile popup dialog. + * Animates the dialog sliding up from the bottom of the screen. + * + * @param {Object} props - Transition props passed by Material-UI + * @param {React.Ref} ref - Forwarded ref for the transition component + * @returns {React.ReactElement} Slide transition component + */ +const SlideTransition = React.forwardRef((props, ref) => ( + // eslint-disable-next-line react/jsx-props-no-spreading -- forwards arbitrary MUI transition props + +)); + /** * Mobile-optimized popup component for displaying location details. * Renders as a bottom sheet dialog that slides up from the bottom of the screen. @@ -13,7 +27,7 @@ import { useLeafletContext } from '@react-leaflet/core'; * @param {React.ReactNode} props.children - Content to display inside the popup dialog * @returns {React.ReactElement} Material-UI Dialog styled as a bottom sheet */ -export const MobilePopup = ({ children }) => { +const MobilePopup = ({ children }) => { const [isOpen, setIsOpen] = useState(true); const context = useLeafletContext(); @@ -29,6 +43,8 @@ export const MobilePopup = ({ children }) => { }; if (overlayContainer) { + // Leaflet's internal position - no public API exposes this. + // eslint-disable-next-line no-underscore-dangle centerMap(overlayContainer._latlng); overlayContainer.on('click', place => { @@ -81,14 +97,8 @@ export const MobilePopup = ({ children }) => { ); }; -/** - * Slide transition component for the mobile popup dialog. - * Animates the dialog sliding up from the bottom of the screen. - * - * @param {Object} props - Transition props passed by Material-UI - * @param {React.Ref} ref - Forwarded ref for the transition component - * @returns {React.ReactElement} Slide transition component - */ -const SlideTransition = React.forwardRef(function Transition(props, ref) { - return ; -}); +MobilePopup.propTypes = { + children: PropTypes.node.isRequired, +}; + +export default MobilePopup; diff --git a/frontend/src/components/MarkerPopup/ReportProblemForm.jsx b/frontend/src/components/MarkerPopup/ReportProblemForm.jsx index 923b7955..86d3491e 100644 --- a/frontend/src/components/MarkerPopup/ReportProblemForm.jsx +++ b/frontend/src/components/MarkerPopup/ReportProblemForm.jsx @@ -145,7 +145,7 @@ const getIssueTypeOptions = t => { ]; }; -export const ReportProblemForm = ({ placeId }) => { +const ReportProblemForm = ({ placeId }) => { const { t } = useTranslation(); const [problem, setProblem] = useState(''); const [problemType, setProblemType] = useState(''); @@ -213,3 +213,5 @@ export const ReportProblemForm = ({ placeId }) => { ReportProblemForm.propTypes = { placeId: PropTypes.string.isRequired, }; + +export default ReportProblemForm; diff --git a/frontend/src/components/common/AppToaster.jsx b/frontend/src/components/common/AppToaster.jsx index 9e1395a1..5ceb523f 100644 --- a/frontend/src/components/common/AppToaster.jsx +++ b/frontend/src/components/common/AppToaster.jsx @@ -12,7 +12,7 @@ import { useMaxToasts } from '../../utils/hooks/useMaxToasts'; * * @returns {React.ReactElement} Toaster component configured for the application */ -export const AppToaster = () => { +const AppToaster = () => { useMaxToasts(); // #main-row's stacking context would trap the toast below the MUI Dialog. @@ -56,3 +56,5 @@ export const AppToaster = () => { document.getElementById('overlay-root'), ); }; + +export default AppToaster; diff --git a/frontend/src/components/common/Autocomplete.jsx b/frontend/src/components/common/Autocomplete.jsx index 5204ea9a..115d6fb7 100644 --- a/frontend/src/components/common/Autocomplete.jsx +++ b/frontend/src/components/common/Autocomplete.jsx @@ -20,13 +20,14 @@ const AutoComplete = ({ onClick }) => { const { t } = useTranslation(); const timeoutRef = useRef(null); - useEffect(() => { - return () => { + useEffect( + () => () => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } - }; - }, []); + }, + [], + ); const handleInputChange = e => { const term = e.target.value; diff --git a/frontend/src/index.js b/frontend/src/index.js index 6752c0f6..16be4c54 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -1,4 +1,4 @@ -import { MapContainer } from './components/Map/Map'; +import MapContainer from './components/Map/Map'; import './i18n'; import { loadPlugins } from './plugins/pluginLoader'; diff --git a/frontend/tests/CategoriesContext.test.jsx b/frontend/tests/CategoriesContext.test.jsx index 1023b993..87509811 100644 --- a/frontend/tests/CategoriesContext.test.jsx +++ b/frontend/tests/CategoriesContext.test.jsx @@ -1,8 +1,8 @@ import React from 'react'; import { render, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom'; -import { FiltersForm } from '../src/components/FiltersForm/FiltersForm'; -import { SuggestNewPointButton } from '../src/components/Map/components/SuggestNewPointButton'; +import FiltersForm from '../src/components/FiltersForm/FiltersForm'; +import SuggestNewPointButton from '../src/components/Map/components/SuggestNewPointButton'; import { CategoriesProvider } from '../src/components/Categories/CategoriesContext'; import { LocationProvider } from '../src/components/Map/context/LocationContext'; import { httpService } from '../src/services/http/httpService'; diff --git a/frontend/tests/FiltersForm.test.jsx b/frontend/tests/FiltersForm.test.jsx index 6eae3bbf..78961a74 100644 --- a/frontend/tests/FiltersForm.test.jsx +++ b/frontend/tests/FiltersForm.test.jsx @@ -1,7 +1,7 @@ import React from 'react'; import '@testing-library/jest-dom'; import { fireEvent, render, waitFor, within } from '@testing-library/react'; -import { FiltersForm } from '../src/components/FiltersForm/FiltersForm'; +import FiltersForm from '../src/components/FiltersForm/FiltersForm'; import { CategoriesProvider } from '../src/components/Categories/CategoriesContext'; import { httpService } from '../src/services/http/httpService'; diff --git a/frontend/tests/Map/MapComponent.test.jsx b/frontend/tests/Map/MapComponent.test.jsx index c7497e8d..bb817654 100644 --- a/frontend/tests/Map/MapComponent.test.jsx +++ b/frontend/tests/Map/MapComponent.test.jsx @@ -1,8 +1,8 @@ import React from 'react'; import { render, waitFor, screen } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; -import { MapComponent } from '../../src/components/Map/MapComponent'; -import { FiltersForm } from '../../src/components/FiltersForm/FiltersForm'; +import MapComponent from '../../src/components/Map/MapComponent'; +import FiltersForm from '../../src/components/FiltersForm/FiltersForm'; import { CategoriesProvider } from '../../src/components/Categories/CategoriesContext'; import { httpService } from '../../src/services/http/httpService'; @@ -46,7 +46,6 @@ describe('MapComponent', () => { }); }); - // eslint-disable-next-line es-x/no-async-functions -- needed to await waitFor it('renders without crashing', async () => { render( @@ -77,7 +76,6 @@ describe('MapComponent with FiltersForm', () => { globalThis.FEATURE_FLAGS = {}; }); - // eslint-disable-next-line es-x/no-async-functions -- needed to await waitFor it('fetches locations once, already filtered by default-checked options', async () => { httpService.getLocations.mockClear(); httpService.getCategoriesData.mockResolvedValueOnce({ diff --git a/frontend/tests/Map/components/SuggestNewPoint.test.jsx b/frontend/tests/Map/components/SuggestNewPoint.test.jsx index 2f2ea080..e4dfdd8a 100644 --- a/frontend/tests/Map/components/SuggestNewPoint.test.jsx +++ b/frontend/tests/Map/components/SuggestNewPoint.test.jsx @@ -2,7 +2,8 @@ import { render, fireEvent, waitFor, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; import React from 'react'; import axios from 'axios'; -import { SuggestNewPointButton } from '../../../src/components/Map/components/SuggestNewPointButton'; +import imageCompression from 'browser-image-compression'; +import SuggestNewPointButton from '../../../src/components/Map/components/SuggestNewPointButton'; import { LocationProvider } from '../../../src/components/Map/context/LocationContext'; import { CategoriesProvider } from '../../../src/components/Categories/CategoriesContext'; import { @@ -20,15 +21,13 @@ import { import { ERROR_MESSAGES, FILE_SIZES, SIMPLE_SCHEMA, FULL_SCHEMA } from '../../utils/testConstants'; import { httpService } from '../../../src/services/http/httpService'; import { toast } from '../../../src/utils/toast'; -import imageCompression from 'browser-image-compression'; -const renderWithProvider = component => { - return render( +const renderWithProvider = component => + render( {component} , ); -}; jest.mock('axios'); jest.mock('../../../src/services/http/httpService', () => ({ diff --git a/frontend/tests/MarkerPopup/CTA.test.jsx b/frontend/tests/MarkerPopup/CTA.test.jsx index 36fefc8e..53d4d192 100644 --- a/frontend/tests/MarkerPopup/CTA.test.jsx +++ b/frontend/tests/MarkerPopup/CTA.test.jsx @@ -1,7 +1,7 @@ import React from 'react'; import '@testing-library/jest-dom'; import { render, fireEvent } from '@testing-library/react'; -import { LocationDetailsBox } from '../../src/components/MarkerPopup/LocationDetails'; +import LocationDetailsBox from '../../src/components/MarkerPopup/LocationDetails'; const correctMarkerData = { title: 'Most Grunwaldzki', diff --git a/frontend/tests/MarkerPopup/ClusterMarker.test.jsx b/frontend/tests/MarkerPopup/ClusterMarker.test.jsx index 0aca93ae..04e13384 100644 --- a/frontend/tests/MarkerPopup/ClusterMarker.test.jsx +++ b/frontend/tests/MarkerPopup/ClusterMarker.test.jsx @@ -2,7 +2,7 @@ import React from 'react'; import '@testing-library/jest-dom'; import { render, screen } from '@testing-library/react'; import { MapContainer } from 'react-leaflet'; -import { ClusterMarker } from '../../src/components/MarkerPopup/ClusterMarker'; +import ClusterMarker from '../../src/components/MarkerPopup/ClusterMarker'; const correctClusterData = { position: [51.1095, 17.0525], diff --git a/frontend/tests/MarkerPopup/LocationDetailsBox.test.jsx b/frontend/tests/MarkerPopup/LocationDetailsBox.test.jsx index b62f0b1f..59297d69 100644 --- a/frontend/tests/MarkerPopup/LocationDetailsBox.test.jsx +++ b/frontend/tests/MarkerPopup/LocationDetailsBox.test.jsx @@ -1,7 +1,7 @@ import React from 'react'; import '@testing-library/jest-dom'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; -import { LocationDetailsBox } from '../../src/components/MarkerPopup/LocationDetails'; +import LocationDetailsBox from '../../src/components/MarkerPopup/LocationDetails'; import { toast } from '../../src/utils/toast'; const correctMarkerData = { diff --git a/frontend/tests/MarkerPopup/MarkerClusterGroupIntegration.test.jsx b/frontend/tests/MarkerPopup/MarkerClusterGroupIntegration.test.jsx index eadee6d8..26b446fd 100644 --- a/frontend/tests/MarkerPopup/MarkerClusterGroupIntegration.test.jsx +++ b/frontend/tests/MarkerPopup/MarkerClusterGroupIntegration.test.jsx @@ -3,7 +3,7 @@ import '@testing-library/jest-dom'; import { render } from '@testing-library/react'; import { MapContainer, TileLayer } from 'react-leaflet'; import MarkerClusterGroup from 'react-leaflet-cluster'; -import { MarkerPopup } from '../../src/components/MarkerPopup/MarkerPopup'; +import MarkerPopup from '../../src/components/MarkerPopup/MarkerPopup'; /** * Integration test for MarkerPopup with MarkerClusterGroup diff --git a/frontend/tests/MarkerPopup/MarkerPopup.test.jsx b/frontend/tests/MarkerPopup/MarkerPopup.test.jsx index 02f82fd6..51cbcb05 100644 --- a/frontend/tests/MarkerPopup/MarkerPopup.test.jsx +++ b/frontend/tests/MarkerPopup/MarkerPopup.test.jsx @@ -2,7 +2,7 @@ import React from 'react'; import '@testing-library/jest-dom'; import { render, screen, fireEvent, act, waitFor } from '@testing-library/react'; import { MapContainer } from 'react-leaflet'; -import { MarkerPopup } from '../../src/components/MarkerPopup/MarkerPopup'; +import MarkerPopup from '../../src/components/MarkerPopup/MarkerPopup'; import { httpService } from '../../src/services/http/httpService'; jest.mock('../../src/services/http/httpService'); diff --git a/frontend/tests/MarkerPopup/ReportProblemForm.test.jsx b/frontend/tests/MarkerPopup/ReportProblemForm.test.jsx index c1a5b3c7..72f44012 100644 --- a/frontend/tests/MarkerPopup/ReportProblemForm.test.jsx +++ b/frontend/tests/MarkerPopup/ReportProblemForm.test.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { render, fireEvent, waitFor } from '@testing-library/react'; -import { ReportProblemForm } from '../../src/components/MarkerPopup/ReportProblemForm'; +import ReportProblemForm from '../../src/components/MarkerPopup/ReportProblemForm'; jest.mock('axios'); const axios = require('axios'); From 6dee804b4b81760d0797c46f601f82ebd9fff216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Ko=C5=82odzi=C5=84ski?= Date: Wed, 19 Aug 2026 16:04:55 +0200 Subject: [PATCH 2/4] linting fixes --- frontend/.eslintrc.json | 15 +++++++++++++++ frontend/package.json | 8 ++++---- .../components/Categories/CategoriesContext.jsx | 2 +- frontend/src/components/Map/MapComponent.jsx | 2 +- .../Map/components/AccessibilityTable.jsx | 2 +- .../components/Map/components/GoToLocation.jsx | 4 ++-- .../src/components/Map/components/Markers.jsx | 2 +- .../Map/components/SaveMapConfiguration.jsx | 2 +- .../Map/components/SuggestNewPointDialog.jsx | 4 ++-- frontend/src/components/Map/map.config.js | 4 +++- frontend/src/components/Map/store/map.store.js | 10 ++++++---- .../components/MarkerPopup/LocationDetails.jsx | 2 +- .../src/components/MarkerPopup/MarkerPopup.jsx | 4 ++-- .../components/MarkerPopup/ReportProblemForm.jsx | 2 +- frontend/src/components/common/AppToaster.jsx | 2 +- frontend/src/index.js | 2 +- frontend/src/plugins/pluginLoader.js | 9 ++++++++- frontend/src/services/hooks/useAutocomplete.js | 2 +- frontend/src/services/http/endpoints.js | 1 - frontend/src/services/http/httpService.js | 6 ++++-- frontend/src/utils/csrf.js | 4 +++- frontend/src/utils/hooks/useMaxToasts.js | 16 ++++++++-------- frontend/src/utils/toast.js | 4 +++- frontend/tests/CategoriesContext.test.jsx | 10 +++++++--- frontend/tests/FiltersForm.test.jsx | 2 +- frontend/tests/Map/MapComponent.test.jsx | 2 +- .../Map/components/AccessibilityTable.test.jsx | 2 +- frontend/tests/Map/components/Markers.test.jsx | 7 ++++--- .../Map/components/SuggestNewPoint.test.jsx | 10 ++++++---- .../MarkerPopup/LocationDetailsBox.test.jsx | 5 +++-- frontend/tests/MarkerPopup/MarkerPopup.test.jsx | 2 +- 31 files changed, 94 insertions(+), 55 deletions(-) diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json index 63736f23..26e4203e 100644 --- a/frontend/.eslintrc.json +++ b/frontend/.eslintrc.json @@ -27,6 +27,21 @@ "no-console": "warn", "func-names": "error", "no-process-exit": "error", + "no-restricted-syntax": [ + "error", + { + "selector": "ForInStatement", + "message": "for..in iterates over the prototype chain; use Object.{keys,values,entries} instead." + }, + { + "selector": "LabeledStatement", + "message": "Labels are a form of GOTO; use functions or early returns instead." + }, + { + "selector": "WithStatement", + "message": "`with` is disallowed in strict mode because it makes scope ambiguous." + } + ], "object-shorthand": "error", "template-curly-spacing": "off", "computed-property-spacing": "off", diff --git a/frontend/package.json b/frontend/package.json index 3635135f..f5d15adc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,10 +15,10 @@ "watch": "webpack --progress -d --config webpack.config.js --watch", "test": "jest --config jest.config.js", "coverage": "jest --coverage --config jest.config.js", - "lint-fix": "eslint src/**/*.jsx tests/**/*.jsx --fix", - "lint": "eslint src/**/*.jsx tests/**/*.jsx", - "prettier-fix": "prettier --write \"src/**/*.jsx\" \"tests/**/*.jsx\"", - "prettier": "prettier --check \"src/**/*.jsx\" \"tests/**/*.jsx\"" + "lint-fix": "eslint \"src/**/*.{js,jsx}\" \"tests/**/*.{js,jsx}\" --fix", + "lint": "eslint \"src/**/*.{js,jsx}\" \"tests/**/*.{js,jsx}\"", + "prettier-fix": "prettier --write \"src/**/*.{js,jsx}\" \"tests/**/*.{js,jsx}\"", + "prettier": "prettier --check \"src/**/*.{js,jsx}\" \"tests/**/*.{js,jsx}\"" }, "files": [ "dist" diff --git a/frontend/src/components/Categories/CategoriesContext.jsx b/frontend/src/components/Categories/CategoriesContext.jsx index 8ec5f2be..624bbce4 100644 --- a/frontend/src/components/Categories/CategoriesContext.jsx +++ b/frontend/src/components/Categories/CategoriesContext.jsx @@ -1,6 +1,6 @@ import React, { useState, useContext, createContext, useMemo, useEffect, useCallback } from 'react'; import PropTypes from 'prop-types'; -import { httpService } from '../../services/http/httpService'; +import httpService from '../../services/http/httpService'; /** * React Context for managing categories state across the application. diff --git a/frontend/src/components/Map/MapComponent.jsx b/frontend/src/components/Map/MapComponent.jsx index 1271ddba..b43eacc9 100644 --- a/frontend/src/components/Map/MapComponent.jsx +++ b/frontend/src/components/Map/MapComponent.jsx @@ -4,7 +4,7 @@ import Control from 'react-leaflet-custom-control'; import LocationControl from './components/LocationControl'; import SuggestNewPointButton from './components/SuggestNewPointButton'; import LocationPermissionBanner from './components/LocationPermissionBanner'; -import { mapConfig } from './map.config'; +import mapConfig from './map.config'; import CustomZoomControl from './components/ZoomControl'; import MapAutocomplete from './components/MapAutocomplete'; import ListViewButton from './components/ListView'; diff --git a/frontend/src/components/Map/components/AccessibilityTable.jsx b/frontend/src/components/Map/components/AccessibilityTable.jsx index 966d909a..3961669f 100644 --- a/frontend/src/components/Map/components/AccessibilityTable.jsx +++ b/frontend/src/components/Map/components/AccessibilityTable.jsx @@ -10,7 +10,7 @@ import Paper from '@mui/material/Paper'; import Arrow from '@mui/icons-material/ArrowLeftRounded'; import { IconButton } from '@mui/material'; import PropTypes from 'prop-types'; -import { httpService } from '../../../services/http/httpService'; +import httpService from '../../../services/http/httpService'; import FieldRenderer from '../../MarkerPopup/FieldRenderer'; import { useCategories } from '../../Categories/CategoriesContext'; diff --git a/frontend/src/components/Map/components/GoToLocation.jsx b/frontend/src/components/Map/components/GoToLocation.jsx index 7ee0ac04..e3edbc0d 100644 --- a/frontend/src/components/Map/components/GoToLocation.jsx +++ b/frontend/src/components/Map/components/GoToLocation.jsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import { useMap } from 'react-leaflet'; -import { httpService } from '../../../services/http/httpService'; -import { useMapStore } from '../store/map.store'; +import httpService from '../../../services/http/httpService'; +import useMapStore from '../store/map.store'; /** * Component that handles navigating to a specific location by ID. diff --git a/frontend/src/components/Map/components/Markers.jsx b/frontend/src/components/Map/components/Markers.jsx index 27247b03..038df6e5 100644 --- a/frontend/src/components/Map/components/Markers.jsx +++ b/frontend/src/components/Map/components/Markers.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import { useMap } from 'react-leaflet'; import MarkerClusterGroup from 'react-leaflet-cluster'; -import { httpService } from '../../../services/http/httpService'; +import httpService from '../../../services/http/httpService'; import MarkerPopup from '../../MarkerPopup/MarkerPopup'; import { useCategories } from '../../Categories/CategoriesContext'; import ClusterMarker from '../../MarkerPopup/ClusterMarker'; diff --git a/frontend/src/components/Map/components/SaveMapConfiguration.jsx b/frontend/src/components/Map/components/SaveMapConfiguration.jsx index 7e7eb608..803ea64a 100644 --- a/frontend/src/components/Map/components/SaveMapConfiguration.jsx +++ b/frontend/src/components/Map/components/SaveMapConfiguration.jsx @@ -1,6 +1,6 @@ import { useEffect } from 'react'; import { useMap } from 'react-leaflet'; -import { useMapStore } from '../store/map.store'; +import useMapStore from '../store/map.store'; /** * Component that saves the current map configuration (zoom level) to the map store. diff --git a/frontend/src/components/Map/components/SuggestNewPointDialog.jsx b/frontend/src/components/Map/components/SuggestNewPointDialog.jsx index cc4c9ade..47a7e41f 100644 --- a/frontend/src/components/Map/components/SuggestNewPointDialog.jsx +++ b/frontend/src/components/Map/components/SuggestNewPointDialog.jsx @@ -25,10 +25,10 @@ import RefreshIcon from '@mui/icons-material/Refresh'; import axios from 'axios'; import { useTranslation } from 'react-i18next'; import imageCompression from 'browser-image-compression'; -import { getCsrfToken } from '../../../utils/csrf'; +import getCsrfToken from '../../../utils/csrf'; import { useLocation } from '../context/LocationContext'; import { useCategories } from '../../Categories/CategoriesContext'; -import { toast } from '../../../utils/toast'; +import toast from '../../../utils/toast'; // Map a category's options to a { key: translation } object. // Options come as [[key, translation], ...] or [key, ...]. diff --git a/frontend/src/components/Map/map.config.js b/frontend/src/components/Map/map.config.js index aa381cda..25a88e66 100644 --- a/frontend/src/components/Map/map.config.js +++ b/frontend/src/components/Map/map.config.js @@ -1,5 +1,7 @@ -export const mapConfig = { +const mapConfig = { initialMapCoordinates: [51.917, 19.013], initialMapZoom: 7, maxMapZoom: 19, }; + +export default mapConfig; diff --git a/frontend/src/components/Map/store/map.store.js b/frontend/src/components/Map/store/map.store.js index 8fe11f42..fa6e4b94 100644 --- a/frontend/src/components/Map/store/map.store.js +++ b/frontend/src/components/Map/store/map.store.js @@ -1,8 +1,10 @@ -import {create} from 'zustand'; +import { create } from 'zustand'; -export const useMapStore = create((set) => ({ +const useMapStore = create(set => ({ mapConfiguration: null, - setMapConfiguration: (mapConfiguration) => set({mapConfiguration}), + setMapConfiguration: mapConfiguration => set({ mapConfiguration }), selectedLocationId: null, - setSelectedLocationId: (selectedLocationId) => set({selectedLocationId}), + setSelectedLocationId: selectedLocationId => set({ selectedLocationId }), })); + +export default useMapStore; diff --git a/frontend/src/components/MarkerPopup/LocationDetails.jsx b/frontend/src/components/MarkerPopup/LocationDetails.jsx index 2fffc2cb..f04d063e 100644 --- a/frontend/src/components/MarkerPopup/LocationDetails.jsx +++ b/frontend/src/components/MarkerPopup/LocationDetails.jsx @@ -10,7 +10,7 @@ import { buttonStyleSmall } from '../../styles/buttonStyle'; import getContentAsString from './fieldContent'; import FieldRenderer from './FieldRenderer'; import ReportProblemForm from './ReportProblemForm'; -import { toast } from '../../utils/toast'; +import toast from '../../utils/toast'; const PopupContainer = styled.div` padding: 8px 4px; diff --git a/frontend/src/components/MarkerPopup/MarkerPopup.jsx b/frontend/src/components/MarkerPopup/MarkerPopup.jsx index 17946f42..6bec59fb 100644 --- a/frontend/src/components/MarkerPopup/MarkerPopup.jsx +++ b/frontend/src/components/MarkerPopup/MarkerPopup.jsx @@ -4,8 +4,8 @@ import { Marker } from 'react-leaflet'; import { isMobile } from 'react-device-detect'; import { Icon } from 'leaflet'; import { useTranslation } from 'react-i18next'; -import { httpService } from '../../services/http/httpService'; -import { useMapStore } from '../Map/store/map.store'; +import httpService from '../../services/http/httpService'; +import useMapStore from '../Map/store/map.store'; import LocationDetailsBox from './LocationDetails'; import MobilePopup from './MobilePopup'; diff --git a/frontend/src/components/MarkerPopup/ReportProblemForm.jsx b/frontend/src/components/MarkerPopup/ReportProblemForm.jsx index 86d3491e..9a2405b0 100644 --- a/frontend/src/components/MarkerPopup/ReportProblemForm.jsx +++ b/frontend/src/components/MarkerPopup/ReportProblemForm.jsx @@ -3,7 +3,7 @@ import React, { useState } from 'react'; import styled from 'styled-components'; import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; -import { getCsrfToken } from '../../utils/csrf'; +import getCsrfToken from '../../utils/csrf'; /** * Styled form component with flexbox column layout. diff --git a/frontend/src/components/common/AppToaster.jsx b/frontend/src/components/common/AppToaster.jsx index 5ceb523f..6a9ac7a9 100644 --- a/frontend/src/components/common/AppToaster.jsx +++ b/frontend/src/components/common/AppToaster.jsx @@ -3,7 +3,7 @@ import { createPortal } from 'react-dom'; import toast, { ToastBar, Toaster } from 'react-hot-toast'; import { IconButton } from '@mui/material'; import Close from '@mui/icons-material/Close'; -import { useMaxToasts } from '../../utils/hooks/useMaxToasts'; +import useMaxToasts from '../../utils/hooks/useMaxToasts'; /** * Global toast notification component for displaying user feedback messages. diff --git a/frontend/src/index.js b/frontend/src/index.js index 16be4c54..40d00372 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -1,6 +1,6 @@ import MapContainer from './components/Map/Map'; import './i18n'; -import { loadPlugins } from './plugins/pluginLoader'; +import loadPlugins from './plugins/pluginLoader'; (async () => { await loadPlugins(); diff --git a/frontend/src/plugins/pluginLoader.js b/frontend/src/plugins/pluginLoader.js index 92d26b21..09331265 100644 --- a/frontend/src/plugins/pluginLoader.js +++ b/frontend/src/plugins/pluginLoader.js @@ -12,7 +12,7 @@ async function loadRemoteScript(url) { }); } -export async function loadPlugins() { +async function loadPlugins() { const manifest = globalThis.PLUGIN_MANIFEST; if (!Array.isArray(manifest) || manifest.length === 0) return; @@ -23,10 +23,14 @@ export async function loadPlugins() { // per-capability module from it. const initialized = new Set(); + // Sequential by design: each container must finish init() against the shared scope before + // the next one loads, so the awaits below cannot be parallelized with Promise.all. + /* eslint-disable no-await-in-loop */ for (const { pluginName, url, module: moduleName, config, capability } of manifest) { try { if (!initialized.has(pluginName)) { await loadRemoteScript(url); + // eslint-disable-next-line camelcase await window[pluginName].init(__webpack_share_scopes__.default); initialized.add(pluginName); } @@ -36,4 +40,7 @@ export async function loadPlugins() { console.warn(`Failed to load plugin "${pluginName}" (${capability}):`, e); } } + /* eslint-enable no-await-in-loop */ } + +export default loadPlugins; diff --git a/frontend/src/services/hooks/useAutocomplete.js b/frontend/src/services/hooks/useAutocomplete.js index 90bdd496..4bee243a 100644 --- a/frontend/src/services/hooks/useAutocomplete.js +++ b/frontend/src/services/hooks/useAutocomplete.js @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { httpService } from '../http/httpService'; +import httpService from '../http/httpService'; /** * Custom hook for address autocomplete functionality. diff --git a/frontend/src/services/http/endpoints.js b/frontend/src/services/http/endpoints.js index 7e1e2d89..b8d0bef1 100644 --- a/frontend/src/services/http/endpoints.js +++ b/frontend/src/services/http/endpoints.js @@ -27,4 +27,3 @@ export const LOCATIONS_CLUSTERED = '/api/locations-clustered'; * Converts addresses/place names to geographic coordinates. */ export const SEARCH_ADDRESS = 'https://nominatim.openstreetmap.org/search'; - diff --git a/frontend/src/services/http/httpService.js b/frontend/src/services/http/httpService.js index 620fb6d7..6a12e810 100644 --- a/frontend/src/services/http/httpService.js +++ b/frontend/src/services/http/httpService.js @@ -5,7 +5,7 @@ import { SEARCH_ADDRESS, LOCATIONS_CLUSTERED, } from './endpoints'; -import { useMapStore } from '../../components/Map/store/map.store'; +import useMapStore from '../../components/Map/store/map.store'; // UUID allowlist used to validate location ids before they are placed in a // request URL. An inline regex (not uuid.validate()) is used deliberately: @@ -63,7 +63,7 @@ function filtersToQuery(filters) { * HTTP service object containing all API interaction methods. * Provides methods for fetching categories, locations, languages, and address search. */ -export const httpService = { +const httpService = { /** * Fetches complete categories data including subcategories in a single request. * Uses the /api/categories-full endpoint to avoid waterfall requests. @@ -208,3 +208,5 @@ export const httpService = { return fetch(`${SEARCH_ADDRESS}?${queryString}`).then(response => response.json()); }, }; + +export default httpService; diff --git a/frontend/src/utils/csrf.js b/frontend/src/utils/csrf.js index a28959fd..a09251ba 100644 --- a/frontend/src/utils/csrf.js +++ b/frontend/src/utils/csrf.js @@ -26,7 +26,7 @@ * headers: { 'X-CSRFToken': csrfToken } * }); */ -export const getCsrfToken = () => { +const getCsrfToken = () => { const metaTag = document.querySelector('meta[name="csrf-token"]'); const token = metaTag?.getAttribute('content'); @@ -38,3 +38,5 @@ export const getCsrfToken = () => { return token; }; + +export default getCsrfToken; diff --git a/frontend/src/utils/hooks/useMaxToasts.js b/frontend/src/utils/hooks/useMaxToasts.js index 346afeec..c565a634 100644 --- a/frontend/src/utils/hooks/useMaxToasts.js +++ b/frontend/src/utils/hooks/useMaxToasts.js @@ -1,5 +1,5 @@ -import React, { useEffect } from "react"; -import toast, {useToasterStore } from "react-hot-toast"; +import { useEffect } from 'react'; +import toast, { useToasterStore } from 'react-hot-toast'; /** * Custom hook that limits the maximum number of visible toasts. @@ -8,15 +8,15 @@ import toast, {useToasterStore } from "react-hot-toast"; * @param {number} max - Maximum number of visible toasts (defaults to 1) * @returns {void} */ -export function useMaxToasts(max = 1) { +function useMaxToasts(max = 1) { const { toasts } = useToasterStore(); - + useEffect(() => { - const excessToasts = toasts - .filter((t) => t.visible) - .filter((_, i) => i >= max); + const excessToasts = toasts.filter(t => t.visible).filter((_, i) => i >= max); for (const t of excessToasts) { toast.dismiss(t.id); } }, [toasts, max]); -} \ No newline at end of file +} + +export default useMaxToasts; diff --git a/frontend/src/utils/toast.js b/frontend/src/utils/toast.js index 4fa99162..f981d913 100644 --- a/frontend/src/utils/toast.js +++ b/frontend/src/utils/toast.js @@ -8,8 +8,10 @@ import { toast as toastify } from 'react-hot-toast'; * @property {Function} error - Displays an error toast notification * @property {Function} info - Displays an info/loading toast notification */ -export const toast = { +const toast = { success: toastify.success, error: toastify.error, info: toastify.loading, }; + +export default toast; diff --git a/frontend/tests/CategoriesContext.test.jsx b/frontend/tests/CategoriesContext.test.jsx index 87509811..d8780fab 100644 --- a/frontend/tests/CategoriesContext.test.jsx +++ b/frontend/tests/CategoriesContext.test.jsx @@ -5,13 +5,17 @@ import FiltersForm from '../src/components/FiltersForm/FiltersForm'; import SuggestNewPointButton from '../src/components/Map/components/SuggestNewPointButton'; import { CategoriesProvider } from '../src/components/Categories/CategoriesContext'; import { LocationProvider } from '../src/components/Map/context/LocationContext'; -import { httpService } from '../src/services/http/httpService'; +import httpService from '../src/services/http/httpService'; jest.mock('axios'); jest.mock('../src/services/http/httpService', () => ({ - httpService: { getCategoriesData: jest.fn(), getLocations: jest.fn() }, + __esModule: true, + default: { getCategoriesData: jest.fn(), getLocations: jest.fn() }, +})); +jest.mock('../src/utils/toast', () => ({ + __esModule: true, + default: { success: jest.fn(), error: jest.fn() }, })); -jest.mock('../src/utils/toast', () => ({ toast: { success: jest.fn(), error: jest.fn() } })); jest.mock('browser-image-compression'); // Both the filter panel and the suggestion dialog need the category definitions. diff --git a/frontend/tests/FiltersForm.test.jsx b/frontend/tests/FiltersForm.test.jsx index 78961a74..1a896c41 100644 --- a/frontend/tests/FiltersForm.test.jsx +++ b/frontend/tests/FiltersForm.test.jsx @@ -3,7 +3,7 @@ import '@testing-library/jest-dom'; import { fireEvent, render, waitFor, within } from '@testing-library/react'; import FiltersForm from '../src/components/FiltersForm/FiltersForm'; import { CategoriesProvider } from '../src/components/Categories/CategoriesContext'; -import { httpService } from '../src/services/http/httpService'; +import httpService from '../src/services/http/httpService'; jest.mock('../src/services/http/httpService'); diff --git a/frontend/tests/Map/MapComponent.test.jsx b/frontend/tests/Map/MapComponent.test.jsx index bb817654..2f307e68 100644 --- a/frontend/tests/Map/MapComponent.test.jsx +++ b/frontend/tests/Map/MapComponent.test.jsx @@ -4,7 +4,7 @@ import '@testing-library/jest-dom/extend-expect'; import MapComponent from '../../src/components/Map/MapComponent'; import FiltersForm from '../../src/components/FiltersForm/FiltersForm'; import { CategoriesProvider } from '../../src/components/Categories/CategoriesContext'; -import { httpService } from '../../src/services/http/httpService'; +import httpService from '../../src/services/http/httpService'; jest.mock('../../src/services/http/httpService'); diff --git a/frontend/tests/Map/components/AccessibilityTable.test.jsx b/frontend/tests/Map/components/AccessibilityTable.test.jsx index 738cc41a..25930438 100644 --- a/frontend/tests/Map/components/AccessibilityTable.test.jsx +++ b/frontend/tests/Map/components/AccessibilityTable.test.jsx @@ -2,7 +2,7 @@ import React from 'react'; import '@testing-library/jest-dom/extend-expect'; import { render, screen, act, waitFor } from '@testing-library/react'; import AccessibilityTable from '../../../src/components/Map/components/AccessibilityTable'; -import { httpService } from '../../../src/services/http/httpService'; +import httpService from '../../../src/services/http/httpService'; import { CategoriesProvider } from '../../../src/components/Categories/CategoriesContext'; diff --git a/frontend/tests/Map/components/Markers.test.jsx b/frontend/tests/Map/components/Markers.test.jsx index 3bac069b..44edffb8 100644 --- a/frontend/tests/Map/components/Markers.test.jsx +++ b/frontend/tests/Map/components/Markers.test.jsx @@ -2,12 +2,13 @@ import React from 'react'; import '@testing-library/jest-dom'; import { render, waitFor } from '@testing-library/react'; import { MapContainer } from 'react-leaflet'; -import { Markers } from '../../../src/components/Map/components/Markers'; +import Markers from '../../../src/components/Map/components/Markers'; import { CategoriesProvider } from '../../../src/components/Categories/CategoriesContext'; -import { httpService } from '../../../src/services/http/httpService'; +import httpService from '../../../src/services/http/httpService'; jest.mock('../../../src/services/http/httpService', () => ({ - httpService: { + __esModule: true, + default: { getCategoriesData: jest.fn(), getLocations: jest.fn(), }, diff --git a/frontend/tests/Map/components/SuggestNewPoint.test.jsx b/frontend/tests/Map/components/SuggestNewPoint.test.jsx index e4dfdd8a..789aad1f 100644 --- a/frontend/tests/Map/components/SuggestNewPoint.test.jsx +++ b/frontend/tests/Map/components/SuggestNewPoint.test.jsx @@ -19,8 +19,8 @@ import { fillTextField, } from '../../utils/dialogHelpers'; import { ERROR_MESSAGES, FILE_SIZES, SIMPLE_SCHEMA, FULL_SCHEMA } from '../../utils/testConstants'; -import { httpService } from '../../../src/services/http/httpService'; -import { toast } from '../../../src/utils/toast'; +import httpService from '../../../src/services/http/httpService'; +import toast from '../../../src/utils/toast'; const renderWithProvider = component => render( @@ -31,12 +31,14 @@ const renderWithProvider = component => jest.mock('axios'); jest.mock('../../../src/services/http/httpService', () => ({ - httpService: { + __esModule: true, + default: { getCategoriesData: jest.fn(), }, })); jest.mock('../../../src/utils/toast', () => ({ - toast: { success: jest.fn(), error: jest.fn() }, + __esModule: true, + default: { success: jest.fn(), error: jest.fn() }, })); jest.mock('browser-image-compression'); diff --git a/frontend/tests/MarkerPopup/LocationDetailsBox.test.jsx b/frontend/tests/MarkerPopup/LocationDetailsBox.test.jsx index 59297d69..4032747b 100644 --- a/frontend/tests/MarkerPopup/LocationDetailsBox.test.jsx +++ b/frontend/tests/MarkerPopup/LocationDetailsBox.test.jsx @@ -2,7 +2,7 @@ import React from 'react'; import '@testing-library/jest-dom'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import LocationDetailsBox from '../../src/components/MarkerPopup/LocationDetails'; -import { toast } from '../../src/utils/toast'; +import toast from '../../src/utils/toast'; const correctMarkerData = { title: 'Most Grunwaldzki', @@ -106,7 +106,8 @@ describe('should render marker popup correctly', () => { }); jest.mock('../../src/utils/toast', () => ({ - toast: { + __esModule: true, + default: { success: jest.fn(), error: jest.fn(), info: jest.fn(), diff --git a/frontend/tests/MarkerPopup/MarkerPopup.test.jsx b/frontend/tests/MarkerPopup/MarkerPopup.test.jsx index 51cbcb05..9689d6ca 100644 --- a/frontend/tests/MarkerPopup/MarkerPopup.test.jsx +++ b/frontend/tests/MarkerPopup/MarkerPopup.test.jsx @@ -3,7 +3,7 @@ import '@testing-library/jest-dom'; import { render, screen, fireEvent, act, waitFor } from '@testing-library/react'; import { MapContainer } from 'react-leaflet'; import MarkerPopup from '../../src/components/MarkerPopup/MarkerPopup'; -import { httpService } from '../../src/services/http/httpService'; +import httpService from '../../src/services/http/httpService'; jest.mock('../../src/services/http/httpService'); From e72fa9e26ece1d9d4b5dd9a873f0d45d79f0d9e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Ko=C5=82odzi=C5=84ski?= Date: Wed, 19 Aug 2026 16:16:56 +0200 Subject: [PATCH 3/4] fix it --- frontend/src/components/MarkerPopup/MobilePopup.jsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/src/components/MarkerPopup/MobilePopup.jsx b/frontend/src/components/MarkerPopup/MobilePopup.jsx index 73ec7337..e666d624 100644 --- a/frontend/src/components/MarkerPopup/MobilePopup.jsx +++ b/frontend/src/components/MarkerPopup/MobilePopup.jsx @@ -43,9 +43,7 @@ const MobilePopup = ({ children }) => { }; if (overlayContainer) { - // Leaflet's internal position - no public API exposes this. - // eslint-disable-next-line no-underscore-dangle - centerMap(overlayContainer._latlng); + centerMap(overlayContainer.getLatLng()); overlayContainer.on('click', place => { centerMap(place.latlng); From 4f3486e49472d6377c2a038cc610806794c51abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Ko=C5=82odzi=C5=84ski?= Date: Wed, 19 Aug 2026 16:19:35 +0200 Subject: [PATCH 4/4] some fixes --- .../components/MarkerPopup/DesktopPopup.jsx | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/MarkerPopup/DesktopPopup.jsx b/frontend/src/components/MarkerPopup/DesktopPopup.jsx index b9628ae9..d065815d 100644 --- a/frontend/src/components/MarkerPopup/DesktopPopup.jsx +++ b/frontend/src/components/MarkerPopup/DesktopPopup.jsx @@ -1,6 +1,7 @@ -import React, { useEffect, useRef } from 'react'; +import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; import { Popup } from 'react-leaflet'; +import { useLeafletContext } from '@react-leaflet/core'; import styled from 'styled-components'; /** @@ -12,28 +13,24 @@ const StyledPopup = styled(Popup)` /** * Desktop popup component for displaying location details. - * Automatically opens the popup on mount using a ref to access the underlying Leaflet marker. - * This is a workaround since react-leaflet's Popup doesn't support lazy loading or .open() function. + * Automatically opens the popup on mount via the parent Marker, which react-leaflet exposes as + * the context's overlay container. This is a workaround since react-leaflet's Popup doesn't + * support lazy loading or an .open() function. * * @param {Object} props - Component props * @param {React.ReactNode} props.children - Content to display inside the popup * @returns {React.ReactElement} Styled Popup component that auto-opens on mount */ const DesktopPopup = ({ children }) => { - const popupRef = useRef(null); + const context = useLeafletContext(); useEffect(() => { - if (popupRef.current) { - // Leaflet's internal Marker reference - no public API exposes this. - // eslint-disable-next-line no-underscore-dangle - const marker = popupRef.current._source; - if (marker) { - marker.openPopup(); - } - } - }, []); + // Popup binds itself to the overlay container in its own effect, and child effects run + // before parent ones, so the popup is already bound by the time this runs. + context.overlayContainer?.openPopup(); + }, [context]); - return {children}; + return {children}; }; DesktopPopup.propTypes = {