From 5cddf5dd188e33bde7b865ce1be8fd6890ce91d9 Mon Sep 17 00:00:00 2001 From: vickystickz Date: Sun, 6 Jul 2025 14:20:34 +0100 Subject: [PATCH 01/22] Fix lint issues and setup husky and lint workflow --- frontend/.husky/pre-commit | 7 + frontend/eslint.config.js | 59 +++-- frontend/package.json | 15 +- frontend/pnpm-lock.yaml | 247 ++++++++++++++++++ frontend/prettier.config.mjs | 4 +- frontend/src/app/providers/auth-provider.tsx | 12 +- .../src/app/providers/models-provider.tsx | 2 +- frontend/src/app/routes/profile/settings.tsx | 49 ++-- frontend/src/app/routes/start-mapping.tsx | 66 ++--- .../landing/core-values/core-values.tsx | 10 +- frontend/src/components/landing/cta/cta.tsx | 2 + frontend/src/components/landing/kpi/kpi.tsx | 10 +- .../map/controls/geolocation-control.tsx | 6 +- .../shared/modals/file-upload-dialog.tsx | 76 +++--- .../src/components/ui/accordion/accordion.tsx | 4 +- frontend/src/components/ui/badge/badge.tsx | 4 +- .../components/ui/drawer/mobile-drawer.tsx | 8 +- frontend/src/components/ui/image/image.tsx | 2 +- frontend/src/components/ui/link/link.tsx | 2 +- frontend/src/config/__tests__/config.test.ts | 2 +- frontend/src/config/index.ts | 64 ++--- frontend/src/constants/toast-notifications.ts | 2 + .../datasets/components/dataset-card.tsx | 22 +- .../components/dataset-list-skeleton.tsx | 2 +- .../drawers/dataset-aoi-edit-drawer.tsx | 2 +- .../datasets/hooks/use-query-params.ts | 6 +- .../model-creation/api/create-trainings.ts | 6 +- .../dialogs/training-area-tour-dialog.tsx | 6 +- .../components/model-summary.tsx | 8 +- .../components/progress-bar.tsx | 12 +- .../components/progress-buttons.tsx | 2 +- .../components/step-heading.tsx | 4 +- .../training-area/open-area-map.tsx | 18 +- .../training-area/training-area-item.tsx | 29 +- .../training-area/training-area-list.tsx | 18 +- .../training-area/training-area-map.tsx | 8 +- .../training-area/training-area.tsx | 26 +- .../training-area/training-labels-offset.tsx | 36 +-- .../training-dataset/create-new.tsx | 24 +- .../training-dataset/select-existing.tsx | 4 +- .../training-dataset/training-dataset.tsx | 2 +- .../training-settings-form.tsx | 26 +- .../training-settings/training-settings.tsx | 2 +- .../hooks/use-training-areas.ts | 10 +- frontend/src/features/models/api/factory.ts | 21 +- .../src/features/models/api/get-models.ts | 2 +- .../src/features/models/api/get-trainings.ts | 12 +- .../features/models/api/update-trainings.ts | 6 +- .../models/components/accuracy-display.tsx | 8 +- .../dialogs/mobile-filters-dialog.tsx | 2 +- .../dialogs/model-details-update-dialog.tsx | 2 +- .../dialogs/model-enhancement-dialog.tsx | 28 +- .../components/dialogs/model-files-dialog.tsx | 2 +- .../dialogs/training-settings-dialog.tsx | 10 +- .../models/components/directory-tree.tsx | 57 ++-- .../models/components/model-details-info.tsx | 20 +- .../components/training-area-drawer.tsx | 12 +- .../map/layers/all-prediction-layer.tsx | 4 +- .../map/layers/prediction-raster-layer.tsx | 6 +- .../components/map/legend-control.tsx | 4 +- .../components/model-settings.tsx | 14 +- ...imagery-source-selector-trigger-button.tsx | 12 +- .../replicable-models/model-selector.tsx | 12 +- .../user-profile/api/notifications.ts | 2 +- .../notifications/notification-bell.tsx | 6 +- .../notifications/notification-item.tsx | 12 +- .../notifications/notifications-panel.tsx | 24 +- .../notifications/user-notifications.tsx | 2 +- .../components/profile-navigation-tabs.tsx | 10 +- .../components/profile-overview.tsx | 20 +- .../components/profile-section-header.tsx | 4 +- .../components/profile-statistics.tsx | 10 +- .../src/hooks/__tests__/use-login.test.ts | 7 +- frontend/src/hooks/use-click-outside.ts | 2 +- frontend/src/hooks/use-clipboard.ts | 10 +- frontend/src/hooks/use-dropdown-menu.ts | 2 +- frontend/src/hooks/use-login.ts | 4 +- frontend/src/hooks/use-map-instance.ts | 4 +- frontend/src/hooks/use-map-layer.ts | 6 +- frontend/src/hooks/use-storage.ts | 19 +- frontend/src/hooks/use-tileservice.ts | 20 +- frontend/src/hooks/use-toast-notification.ts | 42 --- frontend/src/main.tsx | 2 +- frontend/src/services/api-client.ts | 6 +- frontend/src/services/auth.ts | 23 +- frontend/src/store/model-prediction-store.ts | 6 +- frontend/src/types/api.ts | 15 +- .../__tests__/geo/geometry-utils.test.ts | 10 +- .../src/utils/__tests__/regex-utils.test.ts | 84 +++--- frontend/src/utils/date-utils.ts | 4 +- frontend/src/utils/general-utils.ts | 105 +++++--- frontend/src/utils/geo/geo-utils.ts | 28 +- frontend/src/utils/geo/geojson-to-osm.ts | 10 +- frontend/src/utils/geo/geometry-utils.ts | 30 +-- frontend/src/utils/geo/map-utils.ts | 4 +- frontend/src/utils/regex-utils.ts | 2 +- frontend/src/utils/string-utils.ts | 2 +- frontend/test-setup.ts | 8 +- 98 files changed, 997 insertions(+), 708 deletions(-) create mode 100644 frontend/.husky/pre-commit delete mode 100644 frontend/src/hooks/use-toast-notification.ts diff --git a/frontend/.husky/pre-commit b/frontend/.husky/pre-commit new file mode 100644 index 000000000..3a4f351d7 --- /dev/null +++ b/frontend/.husky/pre-commit @@ -0,0 +1,7 @@ +echo "===== Navigating to project directory =====" +cd frontend + +echo "===== Running precommit script =====" +pnpm precommit + +echo "===== Pre-commit hook finished =====" \ No newline at end of file diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index 6b191c342..1d145beb4 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -2,26 +2,36 @@ import js from '@eslint/js'; import globals from 'globals'; import reactHooks from 'eslint-plugin-react-hooks'; import reactRefresh from 'eslint-plugin-react-refresh'; -import tseslint from '@typescript-eslint/eslint-plugin'; import prettierPlugin from 'eslint-plugin-prettier'; import prettierConfig from 'eslint-config-prettier'; +import tseslint from 'typescript-eslint' +import react from 'eslint-plugin-react' +import TypeScriptParser from '@typescript-eslint/parser' +import pluginQuery from '@tanstack/eslint-plugin-query' +import tailwind from "eslint-plugin-tailwindcss"; -export default [ + +export default tseslint.config( + { ignores: ['dist'] }, { - ignores: ['dist'], + extends: [ + // Js + js.configs.recommended, + // Ts + ...tseslint.configs.recommended, + // React + react.configs.flat.recommended, + // Prettier + ...pluginQuery.configs['flat/recommended'], + ...tailwind.configs["flat/recommended"], + prettierConfig, + ], files: ['**/*.{ts,tsx}'], - languageOptions: { - ecmaVersion: 2020, - sourceType: 'module', - globals: globals.browser, - parser: '@typescript-eslint/parser', - }, plugins: { 'react-hooks': reactHooks, 'react-refresh': reactRefresh, - '@tanstack/query': '@tanstack/query', 'prettier': prettierPlugin, }, rules: { @@ -31,12 +41,27 @@ export default [ { allowConstantExport: true }, ], 'prettier/prettier': 'error', + // To avoid the need to import React + 'react/react-in-jsx-scope': 'off', + // To avoid the need to specify prop types + 'react/prop-types': 'off', + 'tailwindcss/no-custom-classname': 'off', + }, + languageOptions: { + ecmaVersion: 2020, + sourceType: 'module', + globals: globals.browser, + parser: TypeScriptParser, + }, + settings: { + // For eslint-plugin-react to auto detect react version + react: { + version: 'detect' + }, }, - }, - js.configs.recommended, - ...tseslint.configs.recommended, - 'plugin:@tanstack/eslint-plugin-query/recommended', - 'plugin:tailwindcss/recommended', - prettierConfig, -]; + + } +) + + diff --git a/frontend/package.json b/frontend/package.json index 8225c2ff3..b4d09a961 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,8 +10,17 @@ "preview": "vite preview", "format": "prettier --write 'src/**/*.{js,jsx,ts,tsx,json,css,scss,md}'", "format:check": "prettier --check 'src/**/*.{js,jsx,ts,tsx,json,css,scss,md}'", - "test": "vitest", - "coverage": "vitest run --coverage" + "test": "vitest run --passWithNoTests", + "coverage": "vitest run --coverage", + "precommit": "lint-staged", + "prepare": "cd .. && husky frontend/.husky" + }, + "lint-staged": { + "src/**/*.{js,jsx,ts,tsx,json,css,scss,md,test.ts}": [ + "pnpm format", + "pnpm test", + "pnpm lint" + ] }, "dependencies": { "@reactour/tour": "^3.7.0", @@ -71,7 +80,9 @@ "eslint-plugin-react-refresh": "^0.4.12", "eslint-plugin-tailwindcss": "^3.17.5", "globals": "^15.10.0", + "husky": "^9.1.7", "jsdom": "^26.0.0", + "lint-staged": "^16.1.2", "postcss": "^8.4.47", "prettier": "3.3.3", "tailwindcss": "^3.4.13", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 42bc43eed..e39f8175e 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -178,9 +178,15 @@ importers: globals: specifier: ^15.10.0 version: 15.10.0 + husky: + specifier: ^9.1.7 + version: 9.1.7 jsdom: specifier: ^26.0.0 version: 26.0.0 + lint-staged: + specifier: ^16.1.2 + version: 16.1.2 postcss: specifier: ^8.4.47 version: 8.4.47 @@ -1281,6 +1287,10 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ansi-escapes@7.0.0: + resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==} + engines: {node: '>=18'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1470,6 +1480,10 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chalk@5.4.1: + resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + character-entities-html4@2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} @@ -1490,6 +1504,14 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-truncate@4.0.0: + resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} + engines: {node: '>=18'} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -1507,6 +1529,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -1514,6 +1539,10 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@14.0.0: + resolution: {integrity: sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==} + engines: {node: '>=20'} + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -1583,6 +1612,15 @@ packages: supports-color: optional: true + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decimal.js@10.5.0: resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==} @@ -1647,6 +1685,9 @@ packages: electron-to-chromium@1.5.30: resolution: {integrity: sha512-sXI35EBN4lYxzc/pIGorlymYNzDBOqkSlVRe6MkgBsW/hW1tpC/HDJ2fjG7XnjakzfLEuvdmux0Mjs6jHq4UOA==} + emoji-regex@10.4.0: + resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1657,6 +1698,10 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + es-abstract@1.23.8: resolution: {integrity: sha512-lfab8IzDn6EpI1ibZakcgS6WsfEBiB+43cuJo+wgylx1xKXf+Sp+YR3vFuQwC/u3sxYwV8Cxe3B0DpVUu/WiJQ==} engines: {node: '>= 0.4'} @@ -1822,6 +1867,9 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + expect-type@1.1.0: resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} engines: {node: '>=12.0.0'} @@ -1941,6 +1989,10 @@ packages: resolution: {integrity: sha512-/Bx5lEn+qRF4TfQ5aLu6NH+UKtvIv7Lhc487y/c8BdludrCTpiWf9wyI0RTyqg49MFefIAvFDuEi5Dfd/zgNxQ==} engines: {node: '>= 0.10'} + get-east-asian-width@1.3.0: + resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==} + engines: {node: '>=18'} + get-intrinsic@1.2.6: resolution: {integrity: sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==} engines: {node: '>= 0.4'} @@ -2054,6 +2106,11 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true + iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -2145,6 +2202,14 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-fullwidth-code-point@4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} + + is-fullwidth-code-point@5.0.0: + resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==} + engines: {node: '>=18'} + is-generator-function@1.0.10: resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} engines: {node: '>= 0.4'} @@ -2311,9 +2376,22 @@ packages: resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} engines: {node: '>=14'} + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + lint-staged@16.1.2: + resolution: {integrity: sha512-sQKw2Si2g9KUZNY3XNvRuDq4UJqpHwF0/FQzZR2M7I5MvtpWvibikCjUVJzZdGE0ByurEl3KQNvsGetd1ty1/Q==} + engines: {node: '>=20.17'} + hasBin: true + + listr2@8.3.3: + resolution: {integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==} + engines: {node: '>=18.0.0'} + lit-element@4.1.0: resolution: {integrity: sha512-gSejRUQJuMQjV2Z59KAS/D4iElUhwKpIyJvZ9w+DIagIQjfJnhR20h2Q5ddpzXGS+fF0tMZ/xEYGMnKmaI/iww==} @@ -2339,6 +2417,10 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + log-update@6.1.0: + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} + engines: {node: '>=18'} + longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -2518,6 +2600,10 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -2551,6 +2637,10 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + nano-spawn@1.0.2: + resolution: {integrity: sha512-21t+ozMQDAL/UGgQVBbZ/xXvNO10++ZPuTmKRO8k9V3AClVRht49ahtDjfY8l1q6nSHOrE5ASfthzH3ol6R/hg==} + engines: {node: '>=20.17'} + nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -2605,6 +2695,10 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -2670,6 +2764,11 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + pidtree@0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true + pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} @@ -2924,10 +3023,17 @@ packages: resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} hasBin: true + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + rollup@4.23.0: resolution: {integrity: sha512-vXB4IT9/KLDrS2WRXmY22sVB2wTsTwkpxjB8Q3mnakTENcYw3FRmfdYDy/acNmls+lHmDazgrRjK/yQ6hQAtwA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -3012,6 +3118,14 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + slice-ansi@5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} + + slice-ansi@7.1.0: + resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==} + engines: {node: '>=18'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -3031,6 +3145,10 @@ packages: std-env@3.8.0: resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -3039,6 +3157,10 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + string.prototype.includes@2.0.1: resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} engines: {node: '>= 0.4'} @@ -3457,6 +3579,10 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + wrap-ansi@9.0.0: + resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} + engines: {node: '>=18'} + ws@8.18.1: resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==} engines: {node: '>=10.0.0'} @@ -3488,6 +3614,11 @@ packages: engines: {node: '>= 14'} hasBin: true + yaml@2.8.0: + resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==} + engines: {node: '>= 14.6'} + hasBin: true + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -4657,6 +4788,10 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ansi-escapes@7.0.0: + dependencies: + environment: 1.1.0 + ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -4866,6 +5001,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + chalk@5.4.1: {} + character-entities-html4@2.1.0: {} character-entities-legacy@3.0.0: {} @@ -4888,6 +5025,15 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-truncate@4.0.0: + dependencies: + slice-ansi: 5.0.0 + string-width: 7.2.0 + clsx@2.1.1: {} color-convert@1.9.3: @@ -4902,12 +5048,16 @@ snapshots: color-name@1.1.4: {} + colorette@2.0.20: {} + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 comma-separated-tokens@2.0.3: {} + commander@14.0.0: {} + commander@4.1.1: {} composed-offset-position@0.0.4: {} @@ -4966,6 +5116,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.4.1: + dependencies: + ms: 2.1.3 + decimal.js@10.5.0: {} decode-named-character-reference@1.0.2: @@ -5022,12 +5176,16 @@ snapshots: electron-to-chromium@1.5.30: {} + emoji-regex@10.4.0: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} entities@4.5.0: {} + environment@1.1.0: {} + es-abstract@1.23.8: dependencies: array-buffer-byte-length: 1.0.2 @@ -5315,6 +5473,8 @@ snapshots: esutils@2.0.3: {} + eventemitter3@5.0.1: {} + expect-type@1.1.0: {} extend@3.0.2: {} @@ -5423,6 +5583,8 @@ snapshots: geojson@0.5.0: {} + get-east-asian-width@1.3.0: {} + get-intrinsic@1.2.6: dependencies: call-bind-apply-helpers: 1.0.1 @@ -5556,6 +5718,8 @@ snapshots: transitivePeerDependencies: - supports-color + husky@9.1.7: {} + iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 @@ -5640,6 +5804,12 @@ snapshots: is-fullwidth-code-point@3.0.0: {} + is-fullwidth-code-point@4.0.0: {} + + is-fullwidth-code-point@5.0.0: + dependencies: + get-east-asian-width: 1.3.0 + is-generator-function@1.0.10: dependencies: has-tostringtag: 1.0.2 @@ -5808,8 +5978,34 @@ snapshots: lilconfig@3.1.2: {} + lilconfig@3.1.3: {} + lines-and-columns@1.2.4: {} + lint-staged@16.1.2: + dependencies: + chalk: 5.4.1 + commander: 14.0.0 + debug: 4.4.1 + lilconfig: 3.1.3 + listr2: 8.3.3 + micromatch: 4.0.8 + nano-spawn: 1.0.2 + pidtree: 0.6.0 + string-argv: 0.3.2 + yaml: 2.8.0 + transitivePeerDependencies: + - supports-color + + listr2@8.3.3: + dependencies: + cli-truncate: 4.0.0 + colorette: 2.0.20 + eventemitter3: 5.0.1 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.0 + lit-element@4.1.0: dependencies: '@lit-labs/ssr-dom-shim': 1.2.1 @@ -5838,6 +6034,14 @@ snapshots: lodash@4.17.21: {} + log-update@6.1.0: + dependencies: + ansi-escapes: 7.0.0 + cli-cursor: 5.0.0 + slice-ansi: 7.1.0 + strip-ansi: 7.1.0 + wrap-ansi: 9.0.0 + longest-streak@3.1.0: {} loose-envify@1.4.0: @@ -6248,6 +6452,8 @@ snapshots: dependencies: mime-db: 1.52.0 + mimic-function@5.0.1: {} + min-indent@1.0.1: {} minimatch@3.1.2: @@ -6278,6 +6484,8 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 + nano-spawn@1.0.2: {} + nanoid@3.3.7: {} natural-compare@1.4.0: {} @@ -6327,6 +6535,10 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.0.0 + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -6397,6 +6609,8 @@ snapshots: picomatch@2.3.1: {} + pidtree@0.6.0: {} + pify@2.3.0: {} pirates@4.0.6: {} @@ -6670,8 +6884,15 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + reusify@1.0.4: {} + rfdc@1.4.1: {} + rollup@4.23.0: dependencies: '@types/estree': 1.0.6 @@ -6787,6 +7008,16 @@ snapshots: signal-exit@4.1.0: {} + slice-ansi@5.0.0: + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 4.0.0 + + slice-ansi@7.1.0: + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 5.0.0 + source-map-js@1.2.1: {} space-separated-tokens@2.0.2: {} @@ -6799,6 +7030,8 @@ snapshots: std-env@3.8.0: {} + string-argv@0.3.2: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -6811,6 +7044,12 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 + string-width@7.2.0: + dependencies: + emoji-regex: 10.4.0 + get-east-asian-width: 1.3.0 + strip-ansi: 7.1.0 + string.prototype.includes@2.0.1: dependencies: call-bind: 1.0.8 @@ -7316,6 +7555,12 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 + wrap-ansi@9.0.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 7.2.0 + strip-ansi: 7.1.0 + ws@8.18.1: {} xml-name-validator@5.0.0: {} @@ -7333,6 +7578,8 @@ snapshots: yaml@2.5.1: {} + yaml@2.8.0: {} + yocto-queue@0.1.0: {} zustand@5.0.3(@types/react@19.1.8)(react@19.1.0): diff --git a/frontend/prettier.config.mjs b/frontend/prettier.config.mjs index 6016970d1..9288b82e5 100644 --- a/frontend/prettier.config.mjs +++ b/frontend/prettier.config.mjs @@ -6,7 +6,9 @@ export default { singleQuote: false, tabWidth: 2, trailingComma: "all", + trailingComma: "es5", printWidth: 80, useTabs: false, - endOfLine: "auto" + endOfLine: "auto", + bracketSpacing: true, }; diff --git a/frontend/src/app/providers/auth-provider.tsx b/frontend/src/app/providers/auth-provider.tsx index 2d9b19145..7f5701a11 100644 --- a/frontend/src/app/providers/auth-provider.tsx +++ b/frontend/src/app/providers/auth-provider.tsx @@ -47,7 +47,7 @@ export const AuthProvider: React.FC = ({ children }) => { useSessionStorage(); const [token, setToken] = useState( - getValue(HOT_FAIR_LOCAL_STORAGE_ACCESS_TOKEN_KEY), + getValue(HOT_FAIR_LOCAL_STORAGE_ACCESS_TOKEN_KEY) ); const [user, setUser] = useState(undefined); @@ -76,7 +76,7 @@ export const AuthProvider: React.FC = ({ children }) => { */ useEffect(() => { const loginSuccessful = getSessionValue( - HOT_FAIR_LOGIN_SUCCESSFUL_SESSION_KEY, + HOT_FAIR_LOGIN_SUCCESSFUL_SESSION_KEY ); if (loginSuccessful == "success") { showSuccessToast(TOAST_NOTIFICATIONS.loginSuccess); @@ -118,7 +118,7 @@ export const AuthProvider: React.FC = ({ children }) => { setUser(user); handleRedirection(); } catch (error) { - showErrorToast(error); + showErrorToast(error as Error); } }; @@ -148,8 +148,8 @@ export const AuthProvider: React.FC = ({ children }) => { const data = await authService.authenticate(state, code); setValue(HOT_FAIR_LOCAL_STORAGE_ACCESS_TOKEN_KEY, data.access_token); setToken(data.access_token); - } catch (error) { - showErrorToast(error, TOAST_NOTIFICATIONS.authenticationFailed); + } catch { + showErrorToast(TOAST_NOTIFICATIONS.authenticationFailed); // Delay for 5 seconds, incase it's the network speed. // Otherwise, redirect the user back to the home page. setTimeout(() => { @@ -177,7 +177,7 @@ export const AuthProvider: React.FC = ({ children }) => { window.location.href = APPLICATION_ROUTES.PROFILE_SETTINGS; }, 3000); } catch (error) { - showErrorToast(error); + showErrorToast(error as Error); // Delay for 3 seconds, incase it's the network speed. // Otherwise, redirect the user back to the home page. setTimeout(() => { diff --git a/frontend/src/app/providers/models-provider.tsx b/frontend/src/app/providers/models-provider.tsx index 213f851a4..e0db5d4b2 100644 --- a/frontend/src/app/providers/models-provider.tsx +++ b/frontend/src/app/providers/models-provider.tsx @@ -563,4 +563,4 @@ export const ModelsProvider: React.FC<{ ); }; -export const useModelsContext = () => useContext(ModelsContext); +export const useModelsContext = () => useContext(ModelsContext); \ No newline at end of file diff --git a/frontend/src/app/routes/profile/settings.tsx b/frontend/src/app/routes/profile/settings.tsx index ce481aa81..64c2aa1cc 100644 --- a/frontend/src/app/routes/profile/settings.tsx +++ b/frontend/src/app/routes/profile/settings.tsx @@ -26,7 +26,7 @@ export const UserProfileSettingsPage = () => { { valid: true, message: "", - }, + } ); const [showForm, setShowForm] = useState(user?.email.length === 0); @@ -40,10 +40,10 @@ export const UserProfileSettingsPage = () => { }>({ monthlyNewsletter: user.newsletter_subscription, emailTrainingNotification: user.notifications_delivery_methods.includes( - NotificationDeliveryMethod.MAIL, + NotificationDeliveryMethod.MAIL ), webTrainingNotification: user.notifications_delivery_methods.includes( - NotificationDeliveryMethod.WEB, + NotificationDeliveryMethod.WEB ), }); @@ -60,7 +60,7 @@ export const UserProfileSettingsPage = () => { setIsEmailPending(false); }, onError: (error) => { - showErrorToast(error, "Error updating email."); + showErrorToast(error); setIsEmailPending(false); }, }, @@ -85,7 +85,7 @@ export const UserProfileSettingsPage = () => { openSuccessDialog(); }, onError: (error) => { - showErrorToast(error, "Account deletion request failed."); + showErrorToast(error); }, }, }); @@ -97,7 +97,7 @@ export const UserProfileSettingsPage = () => { mutationConfig: { onSuccess: () => { showSuccessToast( - "Email verification instructions has been sent to your email address.", + "Email verification instructions has been sent to your email address." ); }, onError: (error) => { @@ -114,7 +114,7 @@ export const UserProfileSettingsPage = () => { setIsNotificationPending(false); }, onError: (error) => { - showErrorToast(error, "Error updating notifications."); + showErrorToast(error); setIsNotificationPending(false); }, }, @@ -133,7 +133,7 @@ export const UserProfileSettingsPage = () => { <> {/* Delete success dialog */} -
+
Model Creation Success Icon { .deletionSuccessTitle } -

+

{ USER_PROFILE_PAGE_CONTENT.settings.account.deleteModal .deletionSuccessDescription @@ -153,7 +153,7 @@ export const UserProfileSettingsPage = () => {

)} -
+
{ rounded />
-
+
{modelPredictionsExist && }
diff --git a/frontend/src/components/landing/core-values/core-values.tsx b/frontend/src/components/landing/core-values/core-values.tsx index 30a199549..b21568cb1 100644 --- a/frontend/src/components/landing/core-values/core-values.tsx +++ b/frontend/src/components/landing/core-values/core-values.tsx @@ -78,7 +78,13 @@ export const Corevalues = () => { {/* Connector */}
- +
{/* Humans not replaced */} @@ -96,6 +102,8 @@ export const Corevalues = () => { src={MapathonOngoing} alt={SHARED_CONTENT.homepage.coreValues.humansNotReplaced.title} className={styles.image} + width="100%" + height="100%" /> {/* The rectangles */}
{ src={HOTTeamTwo} alt={SHARED_CONTENT.homepage.callToAction.ctaButton} className={styles.image} + width="100" + height="100" /> {/* The rectangles */}
diff --git a/frontend/src/components/landing/kpi/kpi.tsx b/frontend/src/components/landing/kpi/kpi.tsx index 2b08c081e..aa28f9cd9 100644 --- a/frontend/src/components/landing/kpi/kpi.tsx +++ b/frontend/src/components/landing/kpi/kpi.tsx @@ -75,14 +75,14 @@ export const Kpi = () => { ]; return ( -
+
{KPIs.map((kpi, id) => (
-
- +
+

{ > {kpi.figure?.toLocaleString()}

-

{kpi.label}

+

{kpi.label}

))} diff --git a/frontend/src/components/map/controls/geolocation-control.tsx b/frontend/src/components/map/controls/geolocation-control.tsx index f8ef5e0b5..f0e800fcf 100644 --- a/frontend/src/components/map/controls/geolocation-control.tsx +++ b/frontend/src/components/map/controls/geolocation-control.tsx @@ -21,8 +21,8 @@ export const GeolocationControl = ({ map }: { map: Map | null }) => { }); }, (error) => { - showErrorToast(error, `Error getting location: ${error.message}.`); - }, + showErrorToast(`Error getting location: ${error.message}.`); + } ); } else { showWarningToast(TOAST_NOTIFICATIONS.geolocationNotSupported); @@ -34,7 +34,7 @@ export const GeolocationControl = ({ map }: { map: Map | null }) => { return ( diff --git a/frontend/src/components/ui/drawer/mobile-drawer.tsx b/frontend/src/components/ui/drawer/mobile-drawer.tsx index 62cfe1891..56a684a51 100644 --- a/frontend/src/components/ui/drawer/mobile-drawer.tsx +++ b/frontend/src/components/ui/drawer/mobile-drawer.tsx @@ -34,7 +34,7 @@ export const MobileDrawer = ({
{canClose ? ( - + {" "} ✕ @@ -57,7 +57,7 @@ export const MobileDrawer = ({
{children}
diff --git a/frontend/src/components/ui/image/image.tsx b/frontend/src/components/ui/image/image.tsx index 414ea5801..de3618be6 100644 --- a/frontend/src/components/ui/image/image.tsx +++ b/frontend/src/components/ui/image/image.tsx @@ -40,7 +40,7 @@ const Image: React.FC = ({ <> {isLoading && (
)} = ({ {nativeAnchor ? ( { it("should trim spaces from a valid input", () => { expect(parseStringEnv(" example.com ", "default.com")).toBe( - "example.com", + "example.com" ); }); }); diff --git a/frontend/src/config/index.ts b/frontend/src/config/index.ts index e7e96dec1..08c6ee652 100644 --- a/frontend/src/config/index.ts +++ b/frontend/src/config/index.ts @@ -12,7 +12,7 @@ import { PredictedFeatureStatus } from "@/enums/start-mapping"; */ export const parseIntEnv = ( value: string | undefined, - defaultValue: number, + defaultValue: number ): number => value !== undefined && !isNaN(parseInt(value, 10)) ? parseInt(value, 10) @@ -23,7 +23,7 @@ export const parseIntEnv = ( */ export const parseFloatEnv = ( value: string | undefined, - defaultValue: number, + defaultValue: number ): number => value !== undefined && !isNaN(parseFloat(value)) ? parseFloat(value) @@ -34,7 +34,7 @@ export const parseFloatEnv = ( */ export const parseStringEnv = ( value: string | undefined, - defaultValue: string, + defaultValue: string ): string => (value && value.trim() !== "" ? value.trim() : defaultValue); // ============================================================================================================================== @@ -47,7 +47,7 @@ export const parseStringEnv = ( */ export const BASE_API_URL: string = parseStringEnv( ENVS.BASE_API_URL, - "http://localhost:8000/api/v1/", + "http://localhost:8000/api/v1/" ); /** @@ -55,7 +55,7 @@ export const BASE_API_URL: string = parseStringEnv( */ export const OAM_TITILER_ENDPOINT: string = parseStringEnv( ENVS.OAM_TITILER_ENDPOINT, - "https://titiler.hotosm.org/", + "https://titiler.hotosm.org/" ); /** @@ -63,7 +63,7 @@ export const OAM_TITILER_ENDPOINT: string = parseStringEnv( */ export const OAM_S3_BUCKET_URL: string = parseStringEnv( ENVS.OAM_S3_BUCKET_URL, - "https://oin-hotosm-temp.s3.us-east-1.amazonaws.com/", + "https://oin-hotosm-temp.s3.us-east-1.amazonaws.com/" ); /** @@ -71,7 +71,7 @@ export const OAM_S3_BUCKET_URL: string = parseStringEnv( */ export const JOSM_REMOTE_URL: string = parseStringEnv( ENVS.JOSM_REMOTE_URL, - "http://127.0.0.1:8111/", + "http://127.0.0.1:8111/" ); /** @@ -79,7 +79,7 @@ export const JOSM_REMOTE_URL: string = parseStringEnv( */ export const OSM_DATABASE_STATUS_API_ENDPOINT: string = parseStringEnv( ENVS.OSM_DATABASE_STATUS_API_URL, - "https://api-prod.raw-data.hotosm.org/v1/status/", + "https://api-prod.raw-data.hotosm.org/v1/status/" ); /** @@ -87,7 +87,7 @@ export const OSM_DATABASE_STATUS_API_ENDPOINT: string = parseStringEnv( */ export const FAIR_PREDICTOR_API_ENDPOINT: string = parseStringEnv( ENVS.FAIR_PREDICTOR_API_URL, - "https://predictor-dev.fair.hotosm.org/predict/", + "https://predictor-dev.fair.hotosm.org/predict/" ); // ============================================================================================================================== @@ -130,7 +130,7 @@ export const HOT_FAIR_BANNER_LOCAL_STORAGE_KEY: string = * The key used to store the predictions for the specific model in the users local storage. */ export const HOT_FAIR_MODEL_PREDICTIONS_LOCAL_STORAGE_KEY = ( - modelId: string, + modelId: string ): string => `__hot_fair_model_predictions_for_model_${modelId}`; /** @@ -148,7 +148,7 @@ export const TRAINING_AREA_TOUR_LOCAL_STORAGE_KEY = */ export const MAX_TRAINING_AREA_SIZE: number = parseIntEnv( ENVS.MAX_TRAINING_AREA_SIZE, - 5000000, + 5000000 ); /** @@ -157,7 +157,7 @@ export const MAX_TRAINING_AREA_SIZE: number = parseIntEnv( */ export const MIN_TRAINING_AREA_SIZE: number = parseIntEnv( ENVS.MIN_TRAINING_AREA_SIZE, - 5797, + 5797 ); /** @@ -182,7 +182,7 @@ export const MAX_GEOJSON_FILE_UPLOAD_FOR_TRAINING_AREA_LABELS: number = */ export const MAX_GEOJSON_FILE_UPLOAD_FOR_TRAINING_AREAS: number = parseIntEnv( ENVS.MAX_GEOJSON_FILE_UPLOAD_FOR_TRAINING_AREAS, - 10, + 10 ); /** @@ -208,7 +208,7 @@ export const MAX_ZOOM_LEVEL: number = parseIntEnv(ENVS.MAX_ZOOM_LEVEL, 21); */ export const MIN_ZOOM_LEVEL_FOR_START_MAPPING_PREDICTION: number = parseIntEnv( ENVS.MIN_ZOOM_LEVEL_FOR_START_MAPPING_PREDICTION, - 18, + 18 ); /** @@ -226,7 +226,7 @@ export const MAP_STYLES_PREFIX: string = "fAIr"; */ export const MIN_ZOOM_LEVEL_FOR_TRAINING_AREA_LABELS: number = parseIntEnv( ENVS.MIN_ZOOM_LEVEL_FOR_TRAINING_AREA_LABELS, - 18, + 18 ); /** @@ -289,40 +289,40 @@ export const PREDICTION_IMAGERY_LAYER_ID: string = `${MAP_STYLES_PREFIX}-predict // Training Areas export const TRAINING_AREAS_AOI_FILL_COLOR: string = parseStringEnv( ENVS.TRAINING_AREAS_AOI_FILL_COLOR, - "#247DCACC", + "#247DCACC" ); export const TRAINING_AREAS_AOI_OUTLINE_COLOR: string = parseStringEnv( ENVS.TRAINING_AREAS_AOI_OUTLINE_COLOR, - "#247DCACC", + "#247DCACC" ); export const TRAINING_AREAS_AOI_OUTLINE_WIDTH: number = parseIntEnv( ENVS.TRAINING_AREAS_AOI_OUTLINE_WIDTH, - 4, + 4 ); export const TRAINING_AREAS_AOI_FILL_OPACITY: number = parseFloatEnv( ENVS.TRAINING_AREAS_AOI_FILL_OPACITY, - 0.4, + 0.4 ); export const TRAINING_AREAS_AOI_LABELS_FILL_OPACITY: number = parseFloatEnv( ENVS.TRAINING_AREAS_AOI_LABELS_FILL_OPACITY, - 0.3, + 0.3 ); export const TRAINING_AREAS_AOI_LABELS_OUTLINE_WIDTH: number = parseIntEnv( ENVS.TRAINING_AREAS_AOI_LABELS_OUTLINE_WIDTH, - 2, + 2 ); export const TRAINING_AREAS_AOI_LABELS_FILL_COLOR: string = parseStringEnv( ENVS.TRAINING_AREAS_AOI_LABELS_FILL_COLOR, - "#D73434", + "#D73434" ); export const TRAINING_AREAS_AOI_LABELS_OUTLINE_COLOR: string = parseStringEnv( ENVS.TRAINING_AREAS_AOI_LABELS_OUTLINE_COLOR, - "#D73434", + "#D73434" ); export const TRAINING_AREAS_MASK_FILL_COLOR: string = parseStringEnv( ENVS.TRAINING_AREAS_MASK_FILL_COLOR, - "#D73434", + "#D73434" ); export const PREDICTIONS_RESULTS_POINT_FILL_COLOR: string = parseStringEnv( @@ -365,7 +365,7 @@ export const MATOMO_ID: string = parseStringEnv(ENVS.MATOMO_ID, "0"); */ export const MATOMO_APP_DOMAIN: string = parseStringEnv( ENVS.MATOMO_APP_DOMAIN, - "fair.hotosm.org", + "fair.hotosm.org" ); /** @@ -373,7 +373,7 @@ export const MATOMO_APP_DOMAIN: string = parseStringEnv( */ export const MATOMO_TRACKING_URL: string = parseStringEnv( ENVS.MATOMO_TRACKING_URL, - "https://matomo.hotosm.org", + "https://matomo.hotosm.org" ); /** @@ -383,7 +383,7 @@ export const HOT_TRACKING_HTML_TAG_NAME: string = "hot-tracking"; export const BANNER_TIMEOUT_DURATION: number = parseIntEnv( ENVS.BANNER_TIMEOUT_DURATION, - 3000, + 3000 ); /** @@ -401,7 +401,7 @@ export const PREDICTION_API_FILE_EXTENSIONS: Record = { */ export const TRAINING_AREA_LABELS_FETCH_POOLING_TIME_MS: number = parseIntEnv( ENVS.TRAINING_AREA_LABELS_FETCH_POOLING_INTERVAL_MS, - 5000, + 5000 ); /** @@ -410,7 +410,7 @@ export const TRAINING_AREA_LABELS_FETCH_POOLING_TIME_MS: number = parseIntEnv( */ export const OSM_LAST_UPDATED_POOLING_INTERVAL_MS: number = parseIntEnv( ENVS.OSM_LAST_UPDATED_POOLING_INTERVAL_MS, - 10000, + 10000 ); /** @@ -431,7 +431,7 @@ export const FAIR_VERSION: string = parseStringEnv(ENVS.FAIR_VERSION, "v0.1"); */ export const OSM_HASHTAGS: string = parseStringEnv( ENVS.OSM_HASHTAGS, - "#HOT-fAIr", + "#HOT-fAIr" ); /** @@ -458,7 +458,7 @@ export const KPI_STATS_CACHE_TIME_MS: number = */ export const MAXIMUM_PREDICTION_TOLERANCE: number = parseIntEnv( ENVS.MAXIMUM_PREDICTION_TOLERANCE, - 10, + 10 ); /** @@ -483,7 +483,7 @@ export const MAXIMUM_ORTHO_SKEW_TOLEARANCE_AND_MAX_ANGLE_CHANGE_IN_DEGREES: numb */ export const FAIR_MODELS_BASE_PATH: string = parseStringEnv( ENVS.FAIR_MODELS_BASE_PATH, - "/mnt/efsmount/data", + "/mnt/efsmount/data" ); export const FAIR_BASE_MODELS_PATH: Record = { diff --git a/frontend/src/constants/toast-notifications.ts b/frontend/src/constants/toast-notifications.ts index c8ee5c11d..9911d9bb1 100644 --- a/frontend/src/constants/toast-notifications.ts +++ b/frontend/src/constants/toast-notifications.ts @@ -52,6 +52,8 @@ export const TOAST_NOTIFICATIONS = { // JOSM errorLoadingData: "An error occurred while loading data", + errorConvertingGeoJSONTOXML: + "An error occurred while converting GeoJSON to OSM XML", dataLoadingSuccess: "Data loaded successfully", josmOpenSuccess: "Map view opened in JOSM successfully!", josmBBOXZoomFailed: "Failed to zoom to the bounding box in JOSM.", diff --git a/frontend/src/features/datasets/components/dataset-card.tsx b/frontend/src/features/datasets/components/dataset-card.tsx index f1fa846a7..2f14010f9 100644 --- a/frontend/src/features/datasets/components/dataset-card.tsx +++ b/frontend/src/features/datasets/components/dataset-card.tsx @@ -51,35 +51,35 @@ export const DatasetCard = ({ }} aria-pressed={selectedDatasetId === dataset.id} aria-label={`Dataset ${dataset.name}`} - className={`w-full relative h-48 border border-gray-border hover:shadow-sm bg-white rounded-lg p-6 flex flex-col justify-between cursor-pointer transition-colors duration-150 ${selectedDatasetId === dataset.id ? "outline outline-primary outline-offset-2" : "hover:border-primary"}`} + className={`relative flex h-48 w-full cursor-pointer flex-col justify-between rounded-lg border border-gray-border bg-white p-6 transition-colors duration-150 hover:shadow-sm ${selectedDatasetId === dataset.id ? "outline outline-offset-2 outline-primary" : "hover:border-primary"}`} > -
+
{selectedDatasetId === dataset.id && ( -
- +
+
)} -

+

{dataset.name}

-

+

ID: {dataset.id}

-
+
-

Used by:

-

+

Used by:

+

{dataset.models_count} Model{dataset.models_count ? "s" : ""}

{showUsername && (
-

+

Created by:

-

+

{dataset.user.username}

diff --git a/frontend/src/features/datasets/components/dataset-list-skeleton.tsx b/frontend/src/features/datasets/components/dataset-list-skeleton.tsx index 08939894f..484cf408a 100644 --- a/frontend/src/features/datasets/components/dataset-list-skeleton.tsx +++ b/frontend/src/features/datasets/components/dataset-list-skeleton.tsx @@ -4,7 +4,7 @@ export const DatasetListSkeleton = () => { {Array.from({ length: 12 }).map((_, index) => (
))}
diff --git a/frontend/src/features/datasets/components/drawers/dataset-aoi-edit-drawer.tsx b/frontend/src/features/datasets/components/drawers/dataset-aoi-edit-drawer.tsx index 77a13c451..26ebd8f17 100644 --- a/frontend/src/features/datasets/components/drawers/dataset-aoi-edit-drawer.tsx +++ b/frontend/src/features/datasets/components/drawers/dataset-aoi-edit-drawer.tsx @@ -21,7 +21,7 @@ export const DatasetAOIEditDrawer: React.FC = ({ label={"Edit Area of Interest"} noHeader={false} > -
+
{isOpened && ( { const debouncedSearchText = useDebounce( query[SEARCH_PARAMS.searchQuery] as string, - 300, + 300 ); const { isPending, isError, data, refetch, isPlaceholderData } = @@ -36,7 +36,7 @@ export const useDatasetsQueryParams = (userId?: number) => { query[SEARCH_PARAMS.offset] !== undefined ? (query[SEARCH_PARAMS.offset] as number) : undefined, - query[SEARCH_PARAMS.id] as number, + query[SEARCH_PARAMS.id] as number ); const updateQuery = useCallback( @@ -57,7 +57,7 @@ export const useDatasetsQueryParams = (userId?: number) => { setSearchParams(updatedParams, { replace: true }); }, - [searchParams, setSearchParams], + [searchParams, setSearchParams] ); //reset offset back to 0 when searching or when ID filtering is applied from the map. diff --git a/frontend/src/features/model-creation/api/create-trainings.ts b/frontend/src/features/model-creation/api/create-trainings.ts index 86674ca9c..f56f7b7ac 100644 --- a/frontend/src/features/model-creation/api/create-trainings.ts +++ b/frontend/src/features/model-creation/api/create-trainings.ts @@ -97,7 +97,7 @@ export type TGetTrainingAreaLabelsFromOSMArgs = { export const getTrainingAreaLabelsFromOSM = async ({ aoiId, -}: TGetTrainingAreaLabelsFromOSMArgs): Promise => { +}: TGetTrainingAreaLabelsFromOSMArgs): Promise => { return await ( await apiClient.post(API_ENDPOINTS.GET_TRAINING_AREA_LABELS_FROM_OSM(aoiId)) ).data; @@ -111,11 +111,11 @@ export type TCreateTrainingLabelsForAOIArgs = { export const createTrainingLabelsForAOI = async ({ aoiId, formData, -}: TCreateTrainingLabelsForAOIArgs): Promise => { +}: TCreateTrainingLabelsForAOIArgs): Promise => { return await ( await apiClient.post( API_ENDPOINTS.UPLOAD_TRAINING_AREA_LABELS(aoiId), - formData, + formData ) ).data.status; }; diff --git a/frontend/src/features/model-creation/components/dialogs/training-area-tour-dialog.tsx b/frontend/src/features/model-creation/components/dialogs/training-area-tour-dialog.tsx index 5321e8823..91c06652b 100644 --- a/frontend/src/features/model-creation/components/dialogs/training-area-tour-dialog.tsx +++ b/frontend/src/features/model-creation/components/dialogs/training-area-tour-dialog.tsx @@ -19,12 +19,12 @@ export const TrainingAreaTourDialog = () => { preventClose >

- This is where you'll define the specific geographical area for your AI - model to analyze. Follow this quick tour to learn how to use this + This is where you'll define the specific geographical area for your + AI model to analyze. Follow this quick tour to learn how to use this section effectively.

diff --git a/frontend/src/features/user-profile/components/profile-overview.tsx b/frontend/src/features/user-profile/components/profile-overview.tsx index f4fdb2c3d..9532692e8 100644 --- a/frontend/src/features/user-profile/components/profile-overview.tsx +++ b/frontend/src/features/user-profile/components/profile-overview.tsx @@ -7,21 +7,21 @@ import { TUser } from "@/types"; export const ProfileOverview = ({ user }: { user: TUser }) => { return ( -
+
-
+
-

+

{user.username}

- + {USER_PROFILE_PAGE_CONTENT.overview.dateJoinedPrefix}{" "} {new Date(user.date_joined).toLocaleString("default", { month: "long", @@ -32,14 +32,14 @@ export const ProfileOverview = ({ user }: { user: TUser }) => {
-

+

{user.profile_completion_percentage} {"%"} {USER_PROFILE_PAGE_CONTENT.overview.profileCompletionSuffix}

-
+
@@ -53,11 +53,11 @@ export const ProfileOverview = ({ user }: { user: TUser }) => { disableLinkStyle title={USER_PROFILE_PAGE_CONTENT.overview.profileCompletionCTA} href={APPLICATION_ROUTES.PROFILE_SETTINGS} - className="!text-primary font-semibold text-body-4 md:text-body-3 inline-flex items-center gap-x-2" + className="inline-flex items-center gap-x-2 text-body-4 font-semibold !text-primary md:text-body-3" > {USER_PROFILE_PAGE_CONTENT.overview.profileCompletionCTA} - + )} diff --git a/frontend/src/features/user-profile/components/profile-section-header.tsx b/frontend/src/features/user-profile/components/profile-section-header.tsx index 36af16460..d1470e93e 100644 --- a/frontend/src/features/user-profile/components/profile-section-header.tsx +++ b/frontend/src/features/user-profile/components/profile-section-header.tsx @@ -17,8 +17,8 @@ export const ProfileSectionHeader = ({ navigate(createRoute as string); }; return ( -
-

+
+

{title}

{createRoute && createButtonAlt && ( diff --git a/frontend/src/features/user-profile/components/profile-statistics.tsx b/frontend/src/features/user-profile/components/profile-statistics.tsx index adf9b3aae..3664ad990 100644 --- a/frontend/src/features/user-profile/components/profile-statistics.tsx +++ b/frontend/src/features/user-profile/components/profile-statistics.tsx @@ -43,12 +43,12 @@ export const ProfileStatistics = () => { ]; return ( -
-
+
+
{profileStatsItems.map((stat) => ( -
-
- +
+
+

diff --git a/frontend/src/hooks/__tests__/use-login.test.ts b/frontend/src/hooks/__tests__/use-login.test.ts index 108066448..6084a6c22 100644 --- a/frontend/src/hooks/__tests__/use-login.test.ts +++ b/frontend/src/hooks/__tests__/use-login.test.ts @@ -58,14 +58,14 @@ describe("useLogin", () => { expect(result.current.loading).toBe(false); expect(setValueMock).toHaveBeenCalledWith( HOT_FAIR_SESSION_REDIRECT_KEY, - pathname, + pathname ); expect(authService.initializeOAuthFlow).toHaveBeenCalled(); }); it("should show error toast if authService.initializeOAuthFlow throws an error", async () => { (authService.initializeOAuthFlow as vi.Mock).mockRejectedValue( - new Error("OAuth error"), + new Error("OAuth error") ); const { result } = renderHook(() => useLogin()); @@ -76,8 +76,7 @@ describe("useLogin", () => { expect(result.current.loading).toBe(false); expect(showErrorToast).toHaveBeenCalledWith( - undefined, - TOAST_NOTIFICATIONS.authenticationFailed, + TOAST_NOTIFICATIONS.authenticationFailed ); }); }); diff --git a/frontend/src/hooks/use-click-outside.ts b/frontend/src/hooks/use-click-outside.ts index b20945e05..ba348fa8d 100644 --- a/frontend/src/hooks/use-click-outside.ts +++ b/frontend/src/hooks/use-click-outside.ts @@ -7,7 +7,7 @@ import { useEffect, useRef } from "react"; * @returns A ref object to be attached to the element you want to detect clicks outside of. */ export const useClickOutside = ( - handler: (event: MouseEvent | TouchEvent) => void, + handler: (event: MouseEvent | TouchEvent) => void ) => { const ref = useRef(null); diff --git a/frontend/src/hooks/use-clipboard.ts b/frontend/src/hooks/use-clipboard.ts index e8ed5ef40..526e60451 100644 --- a/frontend/src/hooks/use-clipboard.ts +++ b/frontend/src/hooks/use-clipboard.ts @@ -1,5 +1,7 @@ import { useState } from "react"; +import { showErrorToast, showSuccessToast } from "@/utils"; + /** * Custom hook to copy text to the clipboard and display a toast notification. * @@ -8,14 +10,18 @@ import { useState } from "react"; * - `copyToClipboard`: A function to copy a given text to the clipboard and display a toast message. */ const useCopyToClipboard = () => { - const [isCopied, setIsCopied] = useState(false); + const [isCopied, setIsCopied] = useState(false); + const copyToClipboard = async (text: string) => { try { await navigator.clipboard.writeText(text); setIsCopied(true); + showSuccessToast("Copied to clipboard!"); + setTimeout(() => setIsCopied(false), 2000); - } catch (error) { + } catch { setIsCopied(false); + showErrorToast("Failed to copy!"); } }; diff --git a/frontend/src/hooks/use-dropdown-menu.ts b/frontend/src/hooks/use-dropdown-menu.ts index 82d215107..96c126bc2 100644 --- a/frontend/src/hooks/use-dropdown-menu.ts +++ b/frontend/src/hooks/use-dropdown-menu.ts @@ -29,6 +29,6 @@ export const useDropdownMenu = () => { onDropdownHide, dropdownRef, }), - [onDropdownShow, onDropdownHide, dropdownRef], + [onDropdownShow, onDropdownHide, dropdownRef] ); }; diff --git a/frontend/src/hooks/use-login.ts b/frontend/src/hooks/use-login.ts index 4a8b47f18..18adfe07c 100644 --- a/frontend/src/hooks/use-login.ts +++ b/frontend/src/hooks/use-login.ts @@ -31,8 +31,8 @@ export const useLogin = () => { setSessionValue(HOT_FAIR_SESSION_REDIRECT_KEY, currentPathWithQuery); try { await authService.initializeOAuthFlow(); - } catch (error) { - showErrorToast(undefined, TOAST_NOTIFICATIONS.authenticationFailed); + } catch { + showErrorToast(TOAST_NOTIFICATIONS.authenticationFailed); } finally { setLoading(false); } diff --git a/frontend/src/hooks/use-map-instance.ts b/frontend/src/hooks/use-map-instance.ts index 42a66a12b..605cb894b 100644 --- a/frontend/src/hooks/use-map-instance.ts +++ b/frontend/src/hooks/use-map-instance.ts @@ -13,12 +13,12 @@ import { useMapStore } from "@/store/map-store"; */ export const useMapInstance = ( pmtiles: boolean = false, - hash: boolean = false, + hash: boolean = false ) => { const mapContainerRef = useRef(null); const [map, setMap] = useState(null); const [drawingMode, setDrawingMode] = useState( - DrawingModes.STATIC, + DrawingModes.STATIC ); const setZoom = useMapStore((state) => state.setZoom); diff --git a/frontend/src/hooks/use-map-layer.ts b/frontend/src/hooks/use-map-layer.ts index 57746c39b..6a023b697 100644 --- a/frontend/src/hooks/use-map-layer.ts +++ b/frontend/src/hooks/use-map-layer.ts @@ -5,7 +5,7 @@ import { useCallback, useEffect } from "react"; export const useMapLayers = ( layersSpec: LayerSpecification[], sourcesSpec: { id: string; spec: SourceSpecification }[], - map: Map | null, + map: Map | null ) => { const addSourcesAndLayers = useCallback(() => { if (!map || !map.isStyleLoaded()) return; @@ -36,9 +36,9 @@ export const useDynamicMapLayer = ( layerId: string, sourceSpec: SourceSpecification | null, layerSpec: LayerSpecification | null, - dependencies: any[] = [], + dependencies: unknown[] = [], enabled: boolean = true, - belowLayerIds: string[] = [], + belowLayerIds: string[] = [] ) => { useEffect(() => { if (!map || !sourceSpec || !layerSpec || !enabled || !map.getStyle()) diff --git a/frontend/src/hooks/use-storage.ts b/frontend/src/hooks/use-storage.ts index 9cc8143cd..11f830d74 100644 --- a/frontend/src/hooks/use-storage.ts +++ b/frontend/src/hooks/use-storage.ts @@ -1,5 +1,4 @@ import { showErrorToast } from "@/utils"; -import { getLocalStorageValue, setLocalStorageValue } from "@/utils/storage"; /** * Custom hook to interact with the browser's localStorage. @@ -15,20 +14,18 @@ import { getLocalStorageValue, setLocalStorageValue } from "@/utils/storage"; export const useLocalStorage = () => { const getValue = (key: string): string | undefined => { try { - const item = getLocalStorageValue(key); + const item = localStorage.getItem(key); return item ? item : undefined; } catch (error) { - showErrorToast(error); + showErrorToast(error as Error); } }; const setValue = (key: string, value: string): void => { try { - setLocalStorageValue(key, value); + localStorage.setItem(key, value); } catch (error) { - showErrorToast( - "Storage limit exceeded. Please clear the predictions or hard refresh the application.", - ); + showErrorToast(error as Error); } }; @@ -36,7 +33,7 @@ export const useLocalStorage = () => { try { localStorage.removeItem(key); } catch (error) { - showErrorToast(error); + showErrorToast(error as Error); } }; @@ -60,7 +57,7 @@ export const useSessionStorage = () => { const item = sessionStorage.getItem(key); return item ? item : undefined; } catch (error) { - showErrorToast(error); + showErrorToast(error as Error); } }; @@ -68,7 +65,7 @@ export const useSessionStorage = () => { try { sessionStorage.setItem(key, value); } catch (error) { - showErrorToast(error); + showErrorToast(error as Error); } }; @@ -76,7 +73,7 @@ export const useSessionStorage = () => { try { sessionStorage.removeItem(key); } catch (error) { - showErrorToast(error); + showErrorToast(error as Error); } }; return { getSessionValue, setSessionValue, removeSessionValue }; diff --git a/frontend/src/hooks/use-tileservice.ts b/frontend/src/hooks/use-tileservice.ts index 6403c42a9..7b1cf85ac 100644 --- a/frontend/src/hooks/use-tileservice.ts +++ b/frontend/src/hooks/use-tileservice.ts @@ -13,16 +13,16 @@ import { useEffect, useMemo, useState } from "react"; export const useTileservice = ( defaultTileServiceType: TileServiceType, - defaultTileserverURL: string, + defaultTileserverURL: string ) => { const [tileServiceType, setTileServiceType] = useState( - defaultTileServiceType, + defaultTileServiceType ); const [loading, setLoading] = useState(false); const [tileJSONMetadata, setTileJSONMetadata] = useState( - null, + null ); const [tileserverURL, setTileserverURL] = @@ -35,11 +35,11 @@ export const useTileservice = ( const currentRegex = useMemo( () => getTileServerRegex(tileServiceType), - [tileServiceType], + [tileServiceType] ); const isValidTileserverURL = useMemo( () => currentRegex.test(tileserverURL), - [tileserverURL, currentRegex], + [tileserverURL, currentRegex] ); /** @@ -79,13 +79,13 @@ export const useTileservice = ( try { setLoading(true); const tileJSON = await getTMSTileJSON( - isOpenAerialMap ? extractTileJSONURL(tileserverURL) : tileserverURL, + isOpenAerialMap ? extractTileJSONURL(tileserverURL) : tileserverURL ); if (tileJSON?.bounds) { setTileJSONMetadata(tileJSON); } - } catch (e) { - showErrorToast(undefined, "Failed to fetch TileJSON metadata."); + } catch { + showErrorToast("Failed to fetch TileJSON metadata."); } finally { setLoading(false); } @@ -172,7 +172,7 @@ export const useTileServiceLayer = ({ }); } catch (e) { setError( - "Unable to load the tile server. Please verify the URL and try again.", + "Unable to load the tile server. Please verify the URL and try again." ); } finally { setLoading(false); @@ -193,7 +193,7 @@ export const useTileServiceLayer = ({ useEffect(() => { if (error) { - showErrorToast(undefined, error); + showErrorToast(error); } }, [error]); diff --git a/frontend/src/hooks/use-toast-notification.ts b/frontend/src/hooks/use-toast-notification.ts deleted file mode 100644 index 21905855a..000000000 --- a/frontend/src/hooks/use-toast-notification.ts +++ /dev/null @@ -1,42 +0,0 @@ -import "@shoelace-style/shoelace/dist/components/alert/alert.js"; - -/** - * Custom hook for displaying toast notifications. - * - * @returns {Function} toast - Function to trigger a toast notification. - * - * @param {string} message - The message to display in the notification. - * @param {"primary" | "success" | "neutral" | "warning" | "danger"} [variant="primary"] - Type of notification style. It defaults to primary. - * @param {number} [duration=3000] - Duration in milliseconds for how long the notification stays visible. - * - * @example - * const toast = useToastNotification(); - * toast("Data saved successfully", "success", 2000); - */ -export const useToastNotification = () => { - const toast = ( - message: string, - variant: - | "primary" - | "success" - | "neutral" - | "warning" - | "danger" = "primary", - duration: number = 3000, - ) => { - const alert = Object.assign(document.createElement("sl-alert"), { - variant, - closable: true, - duration, - innerHTML: ` - ${message} - `, - }); - // make the variant the classname - alert.classList.add(variant); - document.body.append(alert); - alert.toast(); - }; - - return toast; -}; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 7ad661fc0..f283bd376 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -12,5 +12,5 @@ createRoot(document.getElementById("root")!).render( - , + ); diff --git a/frontend/src/services/api-client.ts b/frontend/src/services/api-client.ts index 53a8676b7..cf6f94246 100644 --- a/frontend/src/services/api-client.ts +++ b/frontend/src/services/api-client.ts @@ -30,12 +30,12 @@ apiClient.interceptors.response.use( (error) => { // if unauthorized request, simply clear the local storage to log them out. if (!error.response) { - showErrorToast(undefined, "Network error"); + showErrorToast("Network error"); } if (error.response?.status === 401) { - showErrorToast(undefined, "Unauthorized, logging out..."); + showErrorToast("Unauthorized, logging out..."); localStorage.removeItem(HOT_FAIR_LOCAL_STORAGE_ACCESS_TOKEN_KEY); } return Promise.reject(error); - }, + } ); diff --git a/frontend/src/services/auth.ts b/frontend/src/services/auth.ts index 4f4b08bd0..23aec5c79 100644 --- a/frontend/src/services/auth.ts +++ b/frontend/src/services/auth.ts @@ -1,7 +1,7 @@ import { API_ENDPOINTS } from "@/services/api-routes"; import { apiClient } from "@/services/api-client"; import { showErrorToast } from "@/utils"; -import { TAuthenticate, TLogin, TUser } from "@/types/api"; +import { TAuthenticate, TLogin, TUser, TVerifyEmail } from "@/types/api"; /** * This class encapsulate the various authentication services. @@ -35,8 +35,8 @@ class AuthService { const response = await apiClient.get(API_ENDPOINTS.LOGIN); const oauthUrl: TLogin = await response.data; return oauthUrl; - } catch (error) { - showErrorToast(undefined, "Failed to get OAuth URL"); + } catch { + showErrorToast("Failed to get OAuth URL"); throw new Error("Unable to retrieve login URL."); } } @@ -53,7 +53,7 @@ class AuthService { throw new Error("Popup blocked or not created."); } } catch (error) { - showErrorToast(undefined, "OAuth flow initialization failed"); + showErrorToast("OAuth flow initialization failed"); throw error; } } @@ -66,8 +66,8 @@ class AuthService { try { const response = await apiClient.get(API_ENDPOINTS.USER); return response.data; - } catch (error) { - showErrorToast(undefined, "Failed to fetch user data"); + } catch { + showErrorToast("Failed to fetch user data"); throw new Error("Unable to retrieve user data."); } } @@ -80,11 +80,11 @@ class AuthService { async authenticate(state: string, code: string): Promise { try { const response = await apiClient.get( - `${API_ENDPOINTS.AUTH_CALLBACK}?code=${code}&state=${state}`, + `${API_ENDPOINTS.AUTH_CALLBACK}?code=${code}&state=${state}` ); return response.data; - } catch (error) { - showErrorToast(undefined, "Authentication failed"); + } catch { + showErrorToast("Authentication failed"); throw new Error("Failed to authenticate user."); } } @@ -95,7 +95,7 @@ class AuthService { * @param token The token retrieved from the URL params. * @returns The email verification status from the backend. */ - async verifyEmail(uid: string, token: string): Promise { + async verifyEmail(uid: string, token: string): Promise { try { const response = await apiClient.get(API_ENDPOINTS.VERIFY_EMAIL, { params: { @@ -105,7 +105,8 @@ class AuthService { }); return response.data; } catch (error) { - showErrorToast(error); + showErrorToast("Failed to verify email"); + throw error; } } } diff --git a/frontend/src/store/model-prediction-store.ts b/frontend/src/store/model-prediction-store.ts index 15ea6b617..624140206 100644 --- a/frontend/src/store/model-prediction-store.ts +++ b/frontend/src/store/model-prediction-store.ts @@ -8,7 +8,7 @@ type ModelPredictionState = { updateFeatureStatus: ( id: number, status: PredictedFeatureStatus, - updatedProperties: Partial, + updatedProperties: Partial ) => void; }; @@ -23,9 +23,9 @@ export const useModelPredictionStore = create( ...f, properties: { ...f.properties, status, ...updatedProperties }, } - : f, + : f ); set({ features: updated }); }, - }), + }) ); diff --git a/frontend/src/types/api.ts b/frontend/src/types/api.ts index ad23dba73..84c8891e0 100644 --- a/frontend/src/types/api.ts +++ b/frontend/src/types/api.ts @@ -2,10 +2,19 @@ import { BASE_MODELS, ModelTrainingStatus } from "@/enums"; import { BBOX } from "./common"; import { GeoJsonProperties, Geometry } from "geojson"; import { PredictedFeatureStatus } from "@/enums/start-mapping"; +import { AxiosError, AxiosResponse } from "axios"; /** * This file contains the different types/schema for the API responses from the backend. */ +type ExtendedAxiosResponseData = { + message?: string; + detail?: string; +}; + +export type ExtendedAxiosError = AxiosError & { + response?: AxiosResponse; +}; // Auth and User API response types @@ -17,6 +26,10 @@ export type TAuthenticate = { access_token: string; }; +export type TVerifyEmail = { + message: string; +}; + export type TUser = { date_joined: string; email: string; @@ -181,7 +194,7 @@ export type TTrainingDetails = { export type TTrainingStatus = { id: string; - result: any; + result: unknown; status: "PENDING"; traceback: string; }; diff --git a/frontend/src/utils/__tests__/geo/geometry-utils.test.ts b/frontend/src/utils/__tests__/geo/geometry-utils.test.ts index e706bfa0a..99901fd9b 100644 --- a/frontend/src/utils/__tests__/geo/geometry-utils.test.ts +++ b/frontend/src/utils/__tests__/geo/geometry-utils.test.ts @@ -101,7 +101,7 @@ describe("handleConflation", () => { const result = handleConflation( existingFeatures, newFeatures, - predictionConfig, + predictionConfig ); expect(result.length).toBe(1); expect(result[0].properties.status).toBe("untouched"); @@ -151,7 +151,7 @@ describe("handleConflation", () => { const result = handleConflation( existingFeatures, newFeatures, - predictionConfig, + predictionConfig ); expect(result.length).toBe(1); expect(result[0].properties.status).toBe("untouched"); @@ -202,7 +202,7 @@ describe("handleConflation", () => { const result = handleConflation( existingFeatures, newFeatures, - predictionConfig, + predictionConfig ); expect(result.length).toBe(1); // only the accepted one remains expect(result[0].properties.id).toBe("accepted-id"); @@ -252,7 +252,7 @@ describe("handleConflation", () => { const result = handleConflation( existingFeatures, newFeatures, - predictionConfig, + predictionConfig ); expect(result.length).toBe(1); // only the rejected one remains expect(result[0].properties.id).toBe("rejected-id"); @@ -297,7 +297,7 @@ describe("handleConflation", () => { const result = handleConflation( existingFeatures, newFeatures, - predictionConfig, + predictionConfig ); expect(result.length).toBe(1); expect(result.every((f) => f.properties.status === "untouched")).toBe(true); diff --git a/frontend/src/utils/__tests__/regex-utils.test.ts b/frontend/src/utils/__tests__/regex-utils.test.ts index 1edf6e7c4..74c78075c 100644 --- a/frontend/src/utils/__tests__/regex-utils.test.ts +++ b/frontend/src/utils/__tests__/regex-utils.test.ts @@ -13,14 +13,14 @@ describe("Regex Patterns", () => { it("XYZ_TILESERVER_URL_REGEX_PATTERN matches valid XYZ URLs", () => { expect( XYZ_TILESERVER_URL_REGEX_PATTERN.test( - "http://example.com/tiles/{z}/{x}/{y}?format=png", - ), + "http://example.com/tiles/{z}/{x}/{y}?format=png" + ) ).toBe(true); expect( XYZ_TILESERVER_URL_REGEX_PATTERN.test( - "https://example.com/tiles/{z}/{x}/{y}?format=jpg", - ), + "https://example.com/tiles/{z}/{x}/{y}?format=jpg" + ) ).toBe(true); expect( XYZ_TILESERVER_URL_REGEX_PATTERN.test( @@ -29,82 +29,78 @@ describe("Regex Patterns", () => { ).toBe(true); expect( XYZ_TILESERVER_URL_REGEX_PATTERN.test( - "https://example.com/tiles/path/{z}/{x}/{y}", - ), + "https://example.com/tiles/path/{z}/{x}/{y}" + ) ).toBe(true); expect( XYZ_TILESERVER_URL_REGEX_PATTERN.test( - "http://example.com/tiles/{z}/{x}/{-y}?format=png", - ), + "http://example.com/tiles/{z}/{x}/{-y}?format=png" + ) ).toBe(false); // -y is TMS, not XYZ expect( XYZ_TILESERVER_URL_REGEX_PATTERN.test( - "http://example.com/tiles/{z}/{x}/{y}?format=gif", - ), + "http://example.com/tiles/{z}/{x}/{y}?format=gif" + ) ).toBe(true); expect( XYZ_TILESERVER_URL_REGEX_PATTERN.test( - "https://c.tiles.mapbox.com/v4/mapbox.satellite/{z}/{x}/{y}@2x.jpg?access_token=pk.abc123", - ), + "https://c.tiles.mapbox.com/v4/mapbox.satellite/{z}/{x}/{y}@2x.jpg?access_token=pk.abc123" + ) ).toBe(true); // supports @2x suffix after {y} }); it("TMS_TILESERVER_URL_REGEX_PATTERN matches valid TMS URLs", () => { expect( TMS_TILESERVER_URL_REGEX_PATTERN.test( - "http://example.com/tiles/{z}/{x}/{-y}?format=png", - ), + "http://example.com/tiles/{z}/{x}/{-y}?format=png" + ) ).toBe(true); expect( TMS_TILESERVER_URL_REGEX_PATTERN.test( - "https://example.com/tiles/{z}/{x}/{-y}?format=jpg", - ), + "https://example.com/tiles/{z}/{x}/{-y}?format=jpg" + ) ).toBe(true); expect( TMS_TILESERVER_URL_REGEX_PATTERN.test( - "https://example.com/tiles/path/{z}/{x}/{-y}", - ), + "https://example.com/tiles/path/{z}/{x}/{-y}" + ) ).toBe(true); expect( TMS_TILESERVER_URL_REGEX_PATTERN.test( - "http://example.com/tiles/{z}/{x}/{y}?format=png", - ), + "http://example.com/tiles/{z}/{x}/{y}?format=png" + ) ).toBe(false); // not TMS if {y} not {-y} }); it("TILEJSON_TILESERVER_URL_REGEX_PATTERN matches valid TileJSON URLs", () => { expect( TILEJSON_TILESERVER_URL_REGEX_PATTERN.test( - "http://example.com/tiles.json", - ), + "http://example.com/tiles.json" + ) ).toBe(true); expect( TILEJSON_TILESERVER_URL_REGEX_PATTERN.test( - "https://example.com/path/to/tiles.json", - ), + "https://example.com/path/to/tiles.json" + ) ).toBe(true); expect( TILEJSON_TILESERVER_URL_REGEX_PATTERN.test( - "https://clarity.maptiles.arcgis.tiles.json", - ), + "https://clarity.maptiles.arcgis.tiles.json" + ) ).toBe(false); expect( TILEJSON_TILESERVER_URL_REGEX_PATTERN.test( - "https://example.com/tiles.json?token=abc", - ), + "https://example.com/tiles.json?token=abc" + ) ).toBe(true); expect( - TILEJSON_TILESERVER_URL_REGEX_PATTERN.test( - "http://example.com/tiles.png", - ), + TILEJSON_TILESERVER_URL_REGEX_PATTERN.test("http://example.com/tiles.png") ).toBe(false); expect( - TILEJSON_TILESERVER_URL_REGEX_PATTERN.test( - "ftp://example.com/tiles.json", - ), + TILEJSON_TILESERVER_URL_REGEX_PATTERN.test("ftp://example.com/tiles.json") ).toBe(false); // wrong protocol }); @@ -116,33 +112,33 @@ describe("Regex Patterns", () => { it("VALID_MODEL_CHECKPOINT_PATH matches valid URLs", () => { expect( - VALID_MODEL_CHECKPOINT_PATH.test("http://example.com/model.onnx"), + VALID_MODEL_CHECKPOINT_PATH.test("http://example.com/model.onnx") ).toBe(true); expect( VALID_MODEL_CHECKPOINT_PATH.test( - "https://example.com/path/to/model.tflite", - ), + "https://example.com/path/to/model.tflite" + ) ).toBe(true); expect( - VALID_MODEL_CHECKPOINT_PATH.test("http://example.com/model.pb"), + VALID_MODEL_CHECKPOINT_PATH.test("http://example.com/model.pb") ).toBe(false); }); it("OPENAERIALMAP_TILESERVER_URL_REGEX_PATTERN matches valid URLs", () => { expect( OPENAERIALMAP_TILESERVER_URL_REGEX_PATTERN.test( - "https://tiles.openaerialmap.org/abc123/1/xyz456/{z}/{x}/{y}", - ), + "https://tiles.openaerialmap.org/abc123/1/xyz456/{z}/{x}/{y}" + ) ).toBe(true); expect( OPENAERIALMAP_TILESERVER_URL_REGEX_PATTERN.test( - "https://tiles.openaerialmap.org/abc123/1/xyz456/{z}/{x}/{-y}", - ), + "https://tiles.openaerialmap.org/abc123/1/xyz456/{z}/{x}/{-y}" + ) ).toBe(false); expect( OPENAERIALMAP_TILESERVER_URL_REGEX_PATTERN.test( - "http://tiles.openaerialmap.org/abc123/1/xyz456/{z}/{x}/{y}", - ), + "http://tiles.openaerialmap.org/abc123/1/xyz456/{z}/{x}/{y}" + ) ).toBe(false); }); }); diff --git a/frontend/src/utils/date-utils.ts b/frontend/src/utils/date-utils.ts index ebd55573f..7cc8c5165 100644 --- a/frontend/src/utils/date-utils.ts +++ b/frontend/src/utils/date-utils.ts @@ -40,7 +40,7 @@ export const extractDatePart = (isoString: string) => { export const buildDateFilterQueryString = ( selectedFilter?: DateFilter, startDate?: string, - endDate?: string, + endDate?: string ): Record => { const params: Record = {}; if (startDate) { @@ -78,7 +78,7 @@ export const formatDate = (isoString: string): string => { export const formatDuration = ( startDate: Date, endDate: Date, - maxUnits: number = 4, + maxUnits: number = 4 ): string => { const diff = Math.abs(endDate.getTime() - startDate.getTime()); diff --git a/frontend/src/utils/general-utils.ts b/frontend/src/utils/general-utils.ts index 0e5b64b74..a12a9ef16 100644 --- a/frontend/src/utils/general-utils.ts +++ b/frontend/src/utils/general-utils.ts @@ -1,10 +1,44 @@ +import { AxiosError } from "axios"; + +import "@shoelace-style/shoelace/dist/components/alert/alert.js"; +import { DefaultError } from "@tanstack/react-query"; + +import { ExtendedAxiosError, TModelDetails } from "@/types"; import { FAIR_MODELS_BASE_PATH, PREDICTION_API_FILE_EXTENSIONS, } from "@/config"; import { BASE_MODELS } from "@/enums"; -import { useToastNotification } from "@/hooks/use-toast-notification"; -import { TModelDetails } from "@/types"; + +/** + * Custom function for displaying toast notifications. + * @param {string} message - The message to display in the notification. + * @param {"primary" | "success" | "neutral" | "warning" | "danger"} [variant="primary"] - Type of notification style. It defaults to primary. + * @param {number} [duration=3000] - Duration in milliseconds for how long the notification stays visible. + * + * @example + * createToastNotification("Data saved successfully", "success", 2000); + */ + +export const createToastNotification = ( + message: string, + variant: "primary" | "success" | "neutral" | "warning" | "danger" = "primary", + duration: number = 3000 +) => { + const alert = Object.assign(document.createElement("sl-alert"), { + variant, + closable: true, + duration, + innerHTML: ` + ${message} + `, + }); + // make the variant the classname + alert.classList.add(variant); + document.body.append(alert); + + alert.toast(); +}; /** * Displays an error message as a toast notification. @@ -19,36 +53,36 @@ import { TModelDetails } from "@/types"; * displayed as the toast notification. */ -export const showErrorToast = ( - error: any | undefined = undefined, - customMessage: string | undefined = undefined, -) => { - const toast = useToastNotification(); +export const showErrorToast = (error: string | AxiosError | DefaultError) => { let message = "An unexpected error occurred"; - if (customMessage) { - message = customMessage; - } else if ( - error?.response?.data && - typeof error?.response?.data !== "object" - ) { - message = error?.response?.data; - } else if (error?.response?.data?.error) { - message = error.response.data.error; - } else if (error?.response?.data?.message) { - message = error.response.data.message; - } else if ( - error?.response?.data?.detail && - typeof error?.response?.data?.detail !== "object" - ) { - message = error?.response?.data?.detail; - } else if (error?.response?.data[0]) { - message = error?.response?.data[0]; - } else if (error.response?.statusText) { - message = error.response?.statusText; - } else if (error.message) { - message = error.message; + + if (error) { + // Custom errors + if (typeof error === "string") { + message = error; + // Backend/API errors + } else if ((error as AxiosError).response) { + const axiosError = error as ExtendedAxiosError; + if (typeof axiosError.response?.data === "string") { + message = axiosError.response.data; + // Handle different types of error responses + } else if (axiosError.response?.data?.message) { + message = axiosError.response.data.message; + } else if (axiosError.response?.data?.detail) { + message = axiosError.response.data.detail; + } else if ( + Array.isArray(axiosError.response?.data) && + axiosError.response.data[0] + ) { + message = axiosError.response.data[0]; + } else if (axiosError.response?.statusText) { + message = axiosError.response.statusText; + } + } else if (error.message) { + message = error.message; + } } - toast(message, "danger"); + createToastNotification(message, "danger"); }; /** @@ -57,8 +91,7 @@ export const showErrorToast = ( * @param {string} message - Optional. The message that will be displayed as the toast notification. */ export const showSuccessToast = (message: string = "") => { - const toast = useToastNotification(); - toast(message, "success"); + createToastNotification(message, "success"); }; /** @@ -67,8 +100,7 @@ export const showSuccessToast = (message: string = "") => { * @param {string} message - Optional. The message that will be displayed as the toast notification. */ export const showWarningToast = (message: string = "") => { - const toast = useToastNotification(); - toast(message, "warning"); + createToastNotification(message, "warning"); }; /** @@ -83,14 +115,13 @@ export const uuid4 = function (): string { return v.toString(16); }); }; - /** * * @param modelInfo - The model information object containing dataset ID, training ID, and base model. * @returns {string} - The constructed model checkpoint path. */ export const constructModelCheckpointPath = ( - modelInfo: TModelDetails, + modelInfo: TModelDetails ): string => { const datasetId = modelInfo?.dataset?.id; const trainingId = modelInfo?.published_training; @@ -99,7 +130,7 @@ export const constructModelCheckpointPath = ( if (!datasetId || !trainingId || !fileExtension) { throw new Error( - "Invalid modelInfo provided. Ensure dataset ID, training ID, and base model are defined.", + "Invalid modelInfo provided. Ensure dataset ID, training ID, and base model are defined." ); } // move to environment variable - /mnt/efsmount/data/trainings diff --git a/frontend/src/utils/geo/geo-utils.ts b/frontend/src/utils/geo/geo-utils.ts index 8b9104552..b84b19c6f 100644 --- a/frontend/src/utils/geo/geo-utils.ts +++ b/frontend/src/utils/geo/geo-utils.ts @@ -21,7 +21,7 @@ import { API_ENDPOINTS } from "@/services"; * @param {Feature[]} features - The GeoJSON features. If it's not provided, it creates an empty FeatureCollection. */ export const createFeatureCollection = ( - features: Feature[] = [], + features: Feature[] = [] ): FeatureCollection => { return { type: "FeatureCollection", @@ -43,14 +43,14 @@ export const openInIDEditor = ( features: Feature[], imageryURL: string, datasetId: string, - aoiId: number, + aoiId: number ) => { const bounds = bbox(createFeatureCollection(features)); const [leftLng, bottomLat, rightLng, topLat] = bounds; const centerLat = (bottomLat + topLat) / 2; const centerLng = (leftLng + rightLng) / 2; const zoomLevel = 17; - let idEditorURL = `https://www.openstreetmap.org/edit?editor=id#disable_features=boundaries&gpx=${BASE_API_URL + API_ENDPOINTS.GET_TRAINING_AREA_GPX(aoiId)}&map=${zoomLevel}/${centerLat}/${centerLng}&background=custom:${encodeURIComponent(imageryURL)}&hashtags=#dataset-${datasetId},#TA-${aoiId}`; + const idEditorURL = `https://www.openstreetmap.org/edit?editor=id#disable_features=boundaries&gpx=${BASE_API_URL + API_ENDPOINTS.GET_TRAINING_AREA_GPX(aoiId)}&map=${zoomLevel}/${centerLat}/${centerLng}&background=custom:${encodeURIComponent(imageryURL)}&hashtags=#dataset-${datasetId},#TA-${aoiId}`; window.open(idEditorURL, "_blank", "noreferrer"); }; @@ -86,7 +86,7 @@ export const validateGeoJSONArea = (geojsonFeature: Feature) => { */ export const geoJSONDowloader = ( geojson: FeatureCollection | Feature, - filename: string, + filename: string ) => { const geojsonStr = JSON.stringify(geojson); const blob = new Blob([geojsonStr], { type: "application/json" }); @@ -103,7 +103,7 @@ export const openInJOSM = async ( oamTileName: string, tmsURL: string, features?: Feature[], - toXML = false, + toXML = false ) => { try { const imgURL = new URL(`${JOSM_REMOTE_URL}imagery`); @@ -114,7 +114,7 @@ export const openInJOSM = async ( const imgResponse = await fetch(imgURL); if (!imgResponse.ok) { - showErrorToast(undefined, TOAST_NOTIFICATIONS.josmImageryLoadFailed); + showErrorToast(TOAST_NOTIFICATIONS.josmImageryLoadFailed); return; } @@ -127,12 +127,12 @@ export const openInJOSM = async ( loadurl.searchParams.set("right", String(bounds[2])); loadurl.searchParams.set( "changeset_tags", - `comment=${OSM_HASHTAGS}|source=${oamTileName}`, + `comment=${OSM_HASHTAGS}|source=${oamTileName}` ); await fetch(loadurl); showSuccessToast(TOAST_NOTIFICATIONS.josmOpenSuccess); - } catch (error) { - showErrorToast(undefined, TOAST_NOTIFICATIONS.josmBBOXZoomFailed); + } catch { + showErrorToast(TOAST_NOTIFICATIONS.josmBBOXZoomFailed); } // XML Conversion if (toXML) { @@ -145,13 +145,13 @@ export const openInJOSM = async ( // No need to show success toast since there'll be a success toast later on // This is to avoid multiple toasts showing up at once. if (!response.ok) { - showErrorToast(undefined, TOAST_NOTIFICATIONS.errorLoadingData); + showErrorToast(TOAST_NOTIFICATIONS.errorLoadingData); } - } catch (error) { - showErrorToast(error); + } catch { + showErrorToast(TOAST_NOTIFICATIONS.errorConvertingGeoJSONTOXML); } } - } catch (error) { - showErrorToast(undefined, TOAST_NOTIFICATIONS.josmOpenFailed); + } catch { + showErrorToast(TOAST_NOTIFICATIONS.josmOpenFailed); } }; diff --git a/frontend/src/utils/geo/geojson-to-osm.ts b/frontend/src/utils/geo/geojson-to-osm.ts index d34d79375..c109f6d42 100644 --- a/frontend/src/utils/geo/geojson-to-osm.ts +++ b/frontend/src/utils/geo/geojson-to-osm.ts @@ -49,7 +49,7 @@ export const geojsonToOsmPolygons = (geojson: FeatureCollection): string => { properties || {}, ways, nodes, - nodesIndex, + nodesIndex ); }); @@ -88,12 +88,12 @@ export const geojsonToOsmPolygons = (geojson: FeatureCollection): string => { // Helper Function to Process Polygons const processPolygon = ( coordinates: Position[][], - properties: Record, + properties: Record, ways: Way[], nodes: Node[], - nodesIndex: Record, + nodesIndex: Record ): void => { - coordinates.forEach((ring, _) => { + coordinates.forEach((ring) => { const way = new Way({ ...mapPropertiesToTags(properties), }); @@ -126,7 +126,7 @@ const processPolygon = ( }; const mapPropertiesToTags = ( - properties: Record, + properties: Record ): Record => { const tags: Record = {}; diff --git a/frontend/src/utils/geo/geometry-utils.ts b/frontend/src/utils/geo/geometry-utils.ts index 40fc7847c..372d67c15 100644 --- a/frontend/src/utils/geo/geometry-utils.ts +++ b/frontend/src/utils/geo/geometry-utils.ts @@ -22,7 +22,7 @@ import { PredictedFeatureStatus } from "@/enums/start-mapping"; * @returns {number} The calculated area of the GeoJSON feature in square meters. */ export const calculateGeoJSONArea = ( - geojsonFeature: Feature | FeatureCollection, + geojsonFeature: Feature | FeatureCollection ): number => { return area(geojsonFeature); }; @@ -63,7 +63,7 @@ export function formatAreaInAppropriateUnit(area: number) { */ export const getGeoJSONFeatureBounds = ( - geojsonFeature: Feature, + geojsonFeature: Feature ): LngLatBoundsLike => { return bboxPolygon(geojsonFeature) as [number, number, number, number]; }; @@ -98,7 +98,7 @@ const degrees_to_radians = (degrees: number): number => { const deg2num = ( lat_deg: number, lon_deg: number, - zoom: number, + zoom: number ): { xtile: number; ytile: number } => { const lat_rad = degrees_to_radians(lat_deg); const n = Math.pow(2.0, zoom); @@ -106,7 +106,7 @@ const deg2num = ( const xtile = Math.floor(((lon_deg + 180.0) / 360.0) * n); const ytile = Math.floor( - ((1.0 - Math.asinh(Math.tan(lat_rad)) / Math.PI) / 2.0) * n, + ((1.0 - Math.asinh(Math.tan(lat_rad)) / Math.PI) / 2.0) * n ); return { xtile, ytile }; }; @@ -138,7 +138,7 @@ const radians_to_degrees = (radians: number): number => { const num2deg = ( xtile: number, ytile: number, - zoom: number, + zoom: number ): { lat_deg: number; lon_deg: number } => { const n = Math.pow(2.0, zoom); const lon_deg = (xtile / n) * 360.0 - 180.0; @@ -167,7 +167,7 @@ export const distance = ( lon1: number, lat2: number, lon2: number, - unit: "K" | "N" | "M", + unit: "K" | "N" | "M" ): number => { if (lat1 === lat2 && lon1 === lon2) { return 0; @@ -209,7 +209,7 @@ export const distance = ( const getClosestCorner = ( lat: number, lon: number, - zoom: number, + zoom: number ): { lat_deg: number; lon_deg: number } | null => { const tile = deg2num(lat, lon, zoom); let shortest = Infinity; @@ -240,7 +240,7 @@ const getClosestCorner = ( */ export const approximateGeom = ( coordinates: Position[], - zoom = 19, + zoom = 19 ): [number, number][] => { return coordinates.map(([lon, lat]) => { const closest = getClosestCorner(lat, lon, zoom); @@ -260,7 +260,7 @@ export const approximateGeom = ( */ export const getTileBoundariesGeoJSON = ( map: Map, - zoom: number, + zoom: number ): FeatureCollection => { const bounds = map.getBounds(); @@ -349,7 +349,7 @@ export const snapGeoJSONPolygonToClosestTile = (geometry: Polygon) => { export const handleConflation = ( existingFeatures: TModelPredictionFeature[], newFeatures: Feature[], - predictionConfig: TModelPredictionsConfig, + predictionConfig: TModelPredictionsConfig ): TModelPredictionFeature[] => { const updated = [...existingFeatures]; @@ -357,13 +357,13 @@ export const handleConflation = ( const intersectsAccepted = updated.some( (f) => f.properties.status === PredictedFeatureStatus.ACCEPTED && - booleanIntersects(f, newFeature), + booleanIntersects(f, newFeature) ); const intersectsRejected = updated.some( (f) => f.properties.status === PredictedFeatureStatus.REJECTED && - booleanIntersects(f, newFeature), + booleanIntersects(f, newFeature) ); if (intersectsAccepted || intersectsRejected) { @@ -373,7 +373,7 @@ export const handleConflation = ( const intersectingIndex = updated.findIndex( (f) => f.properties.status === PredictedFeatureStatus.UNTOUCHED && - booleanIntersects(f, newFeature), + booleanIntersects(f, newFeature) ); const featureWithProps: TModelPredictionFeature = { @@ -412,7 +412,7 @@ export const handleConflation = ( */ export const featureIsWithinBounds = ( OAMBounds: LngLatBoundsLike, - feature: Feature, + feature: Feature ): boolean => { const [west, south, east, north] = OAMBounds as [ number, @@ -444,7 +444,7 @@ export const featureIsWithinBounds = ( export const metersToLngLat = ( xMeters: number, yMeters: number, - latitude: number, + latitude: number ): [number, number] => { const deltaLng = xMeters / (111320 * Math.cos((latitude * Math.PI) / 180)); const deltaLat = yMeters / 110540; diff --git a/frontend/src/utils/geo/map-utils.ts b/frontend/src/utils/geo/map-utils.ts index a493510a8..a75777e5a 100644 --- a/frontend/src/utils/geo/map-utils.ts +++ b/frontend/src/utils/geo/map-utils.ts @@ -2,7 +2,7 @@ import { LayerSpecification, Map, SourceSpecification } from "maplibre-gl"; export const addSources = ( map: Map, - sources: { id: string; spec: SourceSpecification }[], + sources: { id: string; spec: SourceSpecification }[] ) => { if (!map) return; sources.forEach((source) => { @@ -14,7 +14,7 @@ export const addSources = ( export const removeSources = ( map: Map, - sources: { id: string; spec: SourceSpecification }[], + sources: { id: string; spec: SourceSpecification }[] ) => { if (!map) return; sources.forEach((source) => { diff --git a/frontend/src/utils/regex-utils.ts b/frontend/src/utils/regex-utils.ts index 39e6451d0..171c2aa8f 100644 --- a/frontend/src/utils/regex-utils.ts +++ b/frontend/src/utils/regex-utils.ts @@ -49,7 +49,7 @@ export const getTileServerRegex = (serviceType: TileServiceType) => { * @returns The type of tile service if matched, otherwise null. */ export const getTileServerTypeFromURL = ( - tileServiceURL: string, + tileServiceURL: string ): TileServiceType => { if (TILEJSON_TILESERVER_URL_REGEX_PATTERN.test(tileServiceURL)) { return TileServiceType.TILEJSON; diff --git a/frontend/src/utils/string-utils.ts b/frontend/src/utils/string-utils.ts index 8599d6ef5..f8f1e6466 100644 --- a/frontend/src/utils/string-utils.ts +++ b/frontend/src/utils/string-utils.ts @@ -24,7 +24,7 @@ export const extractTileJSONURL = (OAMTMSURL: string) => { // we have to grab the unique id of the aerial imagery, construct the new S3 bucket location and give it to titiler to get the new TileJSON. const uniqueImageryId = OAMTMSURL.replace( "https://tiles.openaerialmap.org/", - "", + "" ).replace("/{z}/{x}/{y}", ""); // Construct the URL to fetch the TileJSON from titiler. diff --git a/frontend/test-setup.ts b/frontend/test-setup.ts index 2ce88848d..5a2d41adf 100644 --- a/frontend/test-setup.ts +++ b/frontend/test-setup.ts @@ -1,6 +1,6 @@ -import { vi } from 'vitest'; -import '@testing-library/jest-dom/vitest'; +import { vi } from "vitest"; +import "@testing-library/jest-dom/vitest"; if (!globalThis.URL.createObjectURL) { - globalThis.URL.createObjectURL = vi.fn(() => 'blob:mock-blob'); -} \ No newline at end of file + globalThis.URL.createObjectURL = vi.fn(() => "blob:mock-blob"); +} From abaf8fded0642691b9bb71dc37bf4e854fe0b812 Mon Sep 17 00:00:00 2001 From: vickystickz Date: Sun, 6 Jul 2025 15:18:27 +0100 Subject: [PATCH 02/22] Fix lint issue --- .../src/app/routes/models/confirmation.tsx | 6 +- frontend/src/app/routes/models/feedbacks.tsx | 30 +++++----- .../src/app/routes/models/models-list.tsx | 24 ++++---- .../src/components/shared/layout-toggle.tsx | 2 +- .../src/components/ui/form/input/input.tsx | 12 ++-- frontend/src/components/ui/tab-group.tsx | 6 +- frontend/src/components/ui/table/table.tsx | 12 ++-- .../components/dataset-area-content.tsx | 6 +- .../training-dataset-form.tsx | 25 ++++---- .../components/filters/date-range-filter.tsx | 20 +++---- .../models/components/maps/feedbacks-map.tsx | 6 +- .../models/components/maps/models-map.tsx | 16 ++--- .../features/models/components/model-card.tsx | 30 +++++----- .../models/components/model-detail-user.tsx | 2 +- .../components/model-details-properties.tsx | 18 +++--- .../models/components/model-feedbacks.tsx | 4 +- .../models/components/model-files-button.tsx | 2 +- .../models/components/model-not-found.tsx | 4 +- .../skeletons/model-info-skeleton.tsx | 32 +++++----- .../skeletons/model-list-skeleton.tsx | 18 +++--- .../skeletons/model-properties-skeleton.tsx | 14 ++--- .../skeletons/models-details-skeleton.tsx | 14 ++--- .../components/training-history-table.tsx | 60 +++++++++++-------- .../src/features/models/hooks/use-training.ts | 6 +- .../components/header/logo-with-dropdown.tsx | 8 +-- .../components/header/model-action.tsx | 12 ++-- .../header/model-details-info-button.tsx | 4 +- .../components/header/model-details-info.tsx | 16 ++--- .../map/predicted-feature-popup.tsx | 40 ++++++------- .../components/mobile-drawer.tsx | 26 ++++---- .../imagery-source-selector.tsx | 10 ++-- .../model-selector-trigger-button.tsx | 10 ++-- frontend/src/services/__tests__/auth.test.ts | 21 +++---- frontend/src/types/api.ts | 5 ++ frontend/src/utils/regex-utils.ts | 6 +- 35 files changed, 272 insertions(+), 255 deletions(-) diff --git a/frontend/src/app/routes/models/confirmation.tsx b/frontend/src/app/routes/models/confirmation.tsx index d9f88f57d..62e5c9f09 100644 --- a/frontend/src/app/routes/models/confirmation.tsx +++ b/frontend/src/app/routes/models/confirmation.tsx @@ -26,10 +26,10 @@ export const ModelConfirmationPage = () => { return (
-
+
{ ? MODELS_CONTENT.modelCreation.confirmation.updateDescription : MODELS_CONTENT.modelCreation.confirmation.description}

-
+
{ const { data, isPending, isError } = useModelsContext(); const { data: feedbacksData, isLoading } = useTrainingFeedbacks( - data?.published_training, + data?.published_training ); if (isLoading || isPending || isError) { return (
-
-
+
+
); } @@ -25,20 +25,20 @@ export const ModelFeedbacksPage = () => { <> -
+
-

+

Training ID: {data?.published_training}

-
-
-
+
+
+
-
-

+
+

Feedbacks

-
+
Model Details @@ -50,21 +50,21 @@ export const ModelFeedbacksPage = () => { />
-

+

These are the rejected mapping results for this training by users. Some have comments attached to them.

-
-

+

+

Total Feedbacks: {feedbacksData?.count}

-
+
{ if (mapViewIsActive) { return ( -
+
{ {modelsMapDataIsPending || modelsMapDataIsError || mapData.features.length === 0 ? ( -
+
) : ( @@ -136,10 +136,10 @@ export const ModelsPage = () => {
{/* Filters */} -
+
-
-
+
+
{ /> {/* Mobile filters */} -
+
{ {/* Desktop */}
-
+
{/* Desktop */} {
{isPending ? ( -
+
) : ( -
-
-

+

+
+

{data?.count}{" "} { MODELS_CONTENT.models.modelsList.sortingAndPaginationSection @@ -230,7 +230,7 @@ export const ModelsPage = () => {

{renderContent()} {/* mobile pagination */} -
+