diff --git a/src/Geopilot.Frontend/.eslintrc.cjs b/src/Geopilot.Frontend/.eslintrc.cjs index 07e56ec4..eb9e9d17 100644 --- a/src/Geopilot.Frontend/.eslintrc.cjs +++ b/src/Geopilot.Frontend/.eslintrc.cjs @@ -7,11 +7,14 @@ module.exports = { "plugin:react/jsx-runtime", "plugin:react-hooks/recommended", "plugin:prettier/recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", ], + parser: "@typescript-eslint/parser", + plugins: ["@typescript-eslint", "react-refresh", "prettier"], ignorePatterns: ["dist", ".eslintrc.cjs"], parserOptions: { ecmaVersion: "latest", sourceType: "module" }, - settings: { react: { version: "18.2" } }, - plugins: ["react-refresh", "prettier"], + settings: { react: { version: "detect" } }, rules: { "prettier/prettier": [ "error", @@ -24,6 +27,8 @@ module.exports = { }, ], "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], + "react/react-in-jsx-scope": "off", "react/prop-types": "off", + "react/display-name": "off", }, }; diff --git a/src/Geopilot.Frontend/index.html b/src/Geopilot.Frontend/index.html index d89446cf..342e32b6 100644 --- a/src/Geopilot.Frontend/index.html +++ b/src/Geopilot.Frontend/index.html @@ -8,6 +8,6 @@
- + diff --git a/src/Geopilot.Frontend/package-lock.json b/src/Geopilot.Frontend/package-lock.json index 2b8d2345..505dc989 100644 --- a/src/Geopilot.Frontend/package-lock.json +++ b/src/Geopilot.Frontend/package-lock.json @@ -34,6 +34,9 @@ "devDependencies": { "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", + "@types/react-router-dom": "^5.3.3", + "@typescript-eslint/eslint-plugin": "^7.15.0", + "@typescript-eslint/parser": "^7.15.0", "@vitejs/plugin-react": "^4.0.3", "cypress": "^13.10.0", "cypress-vite": "^1.5.0", @@ -45,7 +48,9 @@ "eslint-plugin-react-refresh": "^0.4.3", "license-checker": "^25.0.1", "prettier": "3.0.3", - "vite": "^4.5.3" + "typescript": "^5.5.3", + "vite": "^4.5.3", + "vite-tsconfig-paths": "^4.3.2" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -1022,18 +1027,18 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz", - "integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -1054,9 +1059,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -1069,9 +1074,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz", - "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1112,13 +1117,14 @@ "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -1139,9 +1145,10 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true }, "node_modules/@jridgewell/gen-mapping": { @@ -1403,9 +1410,9 @@ } }, "node_modules/@mui/types": { - "version": "7.2.9", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.9.tgz", - "integrity": "sha512-k1lN/PolaRZfNsRdAqXtcR71sTnv3z/VCCGPxU8HfdftDkzi335MdJ6scZxvofMAd/K/9EbzCZTFBmlNpQVdCg==", + "version": "7.2.14", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.14.tgz", + "integrity": "sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==", "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0" }, @@ -1729,6 +1736,12 @@ "@types/unist": "*" } }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", + "dev": true + }, "node_modules/@types/mdast": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.2.tgz", @@ -1763,24 +1776,44 @@ "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" }, "node_modules/@types/react": { - "version": "18.2.28", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.28.tgz", - "integrity": "sha512-ad4aa/RaaJS3hyGz0BGegdnSRXQBkd1CCYDCdNjBPg90UUpLgo+WlJqb9fMYUxtehmzF3PJaTWqRZjko6BRzBg==", + "version": "18.3.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", + "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", "dependencies": { "@types/prop-types": "*", - "@types/scheduler": "*", "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.13.tgz", - "integrity": "sha512-eJIUv7rPP+EC45uNYp/ThhSpE16k22VJUknt5OLoH9tbXoi8bMhwLf5xRuWMywamNbWzhrSmU7IBJfPup1+3fw==", + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", "dev": true, "dependencies": { "@types/react": "*" } }, + "node_modules/@types/react-router": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "dev": true, + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dev": true, + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, "node_modules/@types/react-transition-group": { "version": "4.4.8", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.8.tgz", @@ -1789,11 +1822,6 @@ "@types/react": "*" } }, - "node_modules/@types/scheduler": { - "version": "0.16.4", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.4.tgz", - "integrity": "sha512-2L9ifAGl7wmXwP4v3pN4p2FLhD0O1qsJpvKmNin5VA8+UvNVb447UDaAEV6UdrkA+m/Xs58U1RFps44x6TFsVQ==" - }, "node_modules/@types/sinonjs__fake-timers": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", @@ -1831,6 +1859,227 @@ "@types/node": "*" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.15.0.tgz", + "integrity": "sha512-uiNHpyjZtFrLwLDpHnzaDlP3Tt6sGMqTCiqmxaN4n4RP0EfYZDODJyddiFDF44Hjwxr5xAcaYxVKm9QKQFJFLA==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.15.0", + "@typescript-eslint/type-utils": "7.15.0", + "@typescript-eslint/utils": "7.15.0", + "@typescript-eslint/visitor-keys": "7.15.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.15.0.tgz", + "integrity": "sha512-k9fYuQNnypLFcqORNClRykkGOMOj+pV6V91R4GO/l1FDGwpqmSwoOQrOHo3cGaH63e+D3ZiCAOsuS/D2c99j/A==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.15.0", + "@typescript-eslint/types": "7.15.0", + "@typescript-eslint/typescript-estree": "7.15.0", + "@typescript-eslint/visitor-keys": "7.15.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.15.0.tgz", + "integrity": "sha512-Q/1yrF/XbxOTvttNVPihxh1b9fxamjEoz2Os/Pe38OHwxC24CyCqXxGTOdpb4lt6HYtqw9HetA/Rf6gDGaMPlw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.15.0", + "@typescript-eslint/visitor-keys": "7.15.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.15.0.tgz", + "integrity": "sha512-SkgriaeV6PDvpA6253PDVep0qCqgbO1IOBiycjnXsszNTVQe5flN5wR5jiczoEoDEnAqYFSFFc9al9BSGVltkg==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "7.15.0", + "@typescript-eslint/utils": "7.15.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.15.0.tgz", + "integrity": "sha512-aV1+B1+ySXbQH0pLK0rx66I3IkiZNidYobyfn0WFsdGhSXw+P3YOqeTq5GED458SfB24tg+ux3S+9g118hjlTw==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.15.0.tgz", + "integrity": "sha512-gjyB/rHAopL/XxfmYThQbXbzRMGhZzGw6KpcMbfe8Q3nNQKStpxnUKeXb0KiN/fFDR42Z43szs6rY7eHk0zdGQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.15.0", + "@typescript-eslint/visitor-keys": "7.15.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.15.0.tgz", + "integrity": "sha512-hfDMDqaqOqsUVGiEPSMLR/AjTSCsmJwjpKkYQRo1FNbmW4tBwBspYDwO9eh7sKSTwMQgBw9/T4DHudPaqshRWA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.15.0", + "@typescript-eslint/types": "7.15.0", + "@typescript-eslint/typescript-estree": "7.15.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.15.0.tgz", + "integrity": "sha512-Hqgy/ETgpt2L5xueA/zHHIl4fJI2O4XUE9l4+OIfbJIRSnTJb/QscncdqqZzofQegIJugRIF57OJea1khw2SDw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.15.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -1862,9 +2111,9 @@ "dev": true }, "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -2047,6 +2296,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/array.prototype.flat": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", @@ -3249,6 +3507,18 @@ "wrappy": "1" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -3492,18 +3762,19 @@ } }, "node_modules/eslint": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz", - "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.51.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -4332,6 +4603,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -4589,9 +4886,9 @@ ] }, "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, "engines": { "node": ">= 4" @@ -7633,6 +7930,15 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/slice-ansi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", @@ -8110,6 +8416,38 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tsconfck": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.1.tgz", + "integrity": "sha512-00eoI6WY57SvZEVjm13stEVE90VkEdJAFGgpFLTsZbJyW/LwFQ7uQxJHWpZ2hzSWgCPKc9AnBnNP+0X7o3hAmQ==", + "dev": true, + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -8223,10 +8561,9 @@ } }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "peer": true, + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", + "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -8540,6 +8877,25 @@ } } }, + "node_modules/vite-tsconfig-paths": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz", + "integrity": "sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "tsconfck": "^3.0.3" + }, + "peerDependencies": { + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, "node_modules/void-elements": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", diff --git a/src/Geopilot.Frontend/package.json b/src/Geopilot.Frontend/package.json index c2b09535..925312b7 100644 --- a/src/Geopilot.Frontend/package.json +++ b/src/Geopilot.Frontend/package.json @@ -40,6 +40,9 @@ "devDependencies": { "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", + "@types/react-router-dom": "^5.3.3", + "@typescript-eslint/eslint-plugin": "^7.15.0", + "@typescript-eslint/parser": "^7.15.0", "@vitejs/plugin-react": "^4.0.3", "cypress": "^13.10.0", "cypress-vite": "^1.5.0", @@ -51,6 +54,8 @@ "eslint-plugin-react-refresh": "^0.4.3", "license-checker": "^25.0.1", "prettier": "3.0.3", - "vite": "^4.5.3" + "typescript": "^5.5.3", + "vite": "^4.5.3", + "vite-tsconfig-paths": "^4.3.2" } } diff --git a/src/Geopilot.Frontend/src/App.jsx b/src/Geopilot.Frontend/src/App.jsx deleted file mode 100644 index 9bb007a8..00000000 --- a/src/Geopilot.Frontend/src/App.jsx +++ /dev/null @@ -1,251 +0,0 @@ -import { PublicClientApplication } from "@azure/msal-browser"; -import { MsalProvider } from "@azure/msal-react"; -import { useEffect, useMemo, useState } from "react"; -import { Alert } from "react-bootstrap"; -import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom"; -import { Snackbar } from "@mui/material"; -import BannerContent from "./BannerContent"; -import Footer from "./Footer"; -import Home from "./pages/home/Home"; -import Admin from "./pages/admin/Admin"; -import ModalContent from "./ModalContent"; -import "./app.css"; -import { AuthProvider } from "./auth/AuthContext"; -import { AdminTemplate } from "./auth/AdminTemplate"; -import { LoggedOutTemplate } from "./auth/LoggedOutTemplate"; -import { I18nextProvider } from "react-i18next"; -import i18n from "./i18n"; -import { createTheme, ThemeProvider } from "@mui/material/styles"; -import { deDE, enUS, frFR, itIT } from "@mui/material/locale"; -import DeliveryOverview from "./pages/admin/DeliveryOverview.jsx"; -import Users from "./pages/admin/Users.jsx"; -import Mandates from "./pages/admin/Mandates.jsx"; -import Organisations from "./pages/admin/Organisations.jsx"; -import { PromptProvider } from "./components/prompt/promptContext.jsx"; -import { Prompt } from "./components/prompt/prompt.jsx"; -import { AlertProvider } from "./components/alert/alertContext.jsx"; -import { AlertBanner } from "./components/alert/alertBanner.jsx"; - -export const App = () => { - const [modalContent, setModalContent] = useState(false); - const [modalContentType, setModalContentType] = useState(null); - const [showModalContent, setShowModalContent] = useState(false); - const [showBannerContent, setShowBannerContent] = useState(false); - const [clientSettings, setClientSettings] = useState({}); - const [auth, setAuth] = useState(undefined); - const [backendVersion, setBackendVersion] = useState(""); - const [datenschutzContent, setDatenschutzContent] = useState(null); - const [impressumContent, setImpressumContent] = useState(null); - const [infoHilfeContent, setInfoHilfeContent] = useState(null); - const [bannerContent, setBannerContent] = useState(null); - const [nutzungsbestimmungenContent, setNutzungsbestimmungenContent] = useState(null); - const [quickStartContent, setQuickStartContent] = useState(null); - const [licenseInfo, setLicenseInfo] = useState(null); - const [licenseInfoCustom, setLicenseInfoCustom] = useState(null); - const [alertText, setAlertText] = useState(""); - const [language, setLanguage] = useState(null); - const [theme, setTheme] = useState({}); - - // Update HTML title property - useEffect(() => { - document.title = clientSettings?.application?.name + " " + backendVersion; - }, [clientSettings, backendVersion]); - - useEffect(() => { - const link = document.querySelector("link[rel=icon]"); - const faviconHref = clientSettings?.application?.favicon; - if (faviconHref) { - link.setAttribute("href", faviconHref); - } - }, [clientSettings]); - - // Fetch client settings - useEffect(() => { - fetch("client-settings.json") - .then(res => res.headers.get("content-type")?.includes("application/json") && res.json()) - .then(setClientSettings); - }, []); - - useEffect(() => { - fetch("/api/v1/user/auth") - .then(res => res.headers.get("content-type")?.includes("application/json") && res.json()) - .then(setAuth); - }, []); - - useEffect(() => { - fetch("api/v1/version") - .then(res => res.headers.get("content-type")?.includes("text/plain") && res.text()) - .then(version => setBackendVersion(version)); - }, []); - - // Fetch optional custom content - useEffect(() => { - fetch("impressum.md") - .then(res => res.headers.get("content-type")?.includes("ext/markdown") && res.text()) - .then(text => setImpressumContent(text)); - - fetch("datenschutz.md") - .then(res => res.headers.get("content-type")?.includes("ext/markdown") && res.text()) - .then(text => setDatenschutzContent(text)); - - fetch("info-hilfe.md") - .then(res => res.headers.get("content-type")?.includes("ext/markdown") && res.text()) - .then(text => setInfoHilfeContent(text)); - - fetch("nutzungsbestimmungen.md") - .then(res => res.headers.get("content-type")?.includes("ext/markdown") && res.text()) - .then(text => setNutzungsbestimmungenContent(text)); - - fetch("banner.md") - .then(res => res.headers.get("content-type")?.includes("ext/markdown") && res.text()) - .then(text => setBannerContent(text)); - - fetch("quickstart.txt") - .then(res => res.headers.get("content-type")?.includes("text/plain") && res.text()) - .then(text => setQuickStartContent(text)); - - fetch("license.json") - .then(res => res.headers.get("content-type")?.includes("application/json") && res.json()) - .then(json => setLicenseInfo(json)); - - fetch("license.custom.json") - .then(res => res.headers.get("content-type")?.includes("application/json") && res.json()) - .then(json => setLicenseInfoCustom(json)); - }, []); - - useEffect(() => { - let baseTheme = {}; - if (clientSettings && clientSettings.theme) { - baseTheme = clientSettings.theme; - } - - let lng = enUS; - switch (language) { - case "de": - lng = deDE; - break; - case "fr": - lng = frFR; - break; - case "it": - lng = itIT; - break; - case "en": - lng = enUS; - break; - } - setTheme(createTheme(baseTheme, lng)); - }, [clientSettings, language]); - - const openModalContent = (content, type) => - setModalContent(content) & setModalContentType(type) & setShowModalContent(true); - - const authCache = clientSettings?.authCache; - const msalInstance = useMemo(() => { - return new PublicClientApplication({ - auth, - cache: authCache, - }); - }, [auth, authCache]); - - useEffect(() => { - const handleLanguageChange = lng => { - setLanguage(lng); - }; - - i18n.on("languageChanged", handleLanguageChange); - - return () => { - i18n.off("languageChanged", handleLanguageChange); - }; - }, []); - - return ( - - - - - - - - -
- - - - openModalContent(nutzungsbestimmungenContent, "markdown")} - quickStartContent={quickStartContent} - setShowBannerContent={setShowBannerContent} - /> -
- - } - /> - - - - } /> - - - - - } /> - }> - } /> - } /> - } /> - } /> - - - - - setShowModalContent(false)} - /> - {bannerContent && showBannerContent && ( - setShowBannerContent(false)} - /> - )} -
- - setAlertText("")}> -

{alertText}

-
-
-
-
-
-
-
-
- ); -}; - -export default App; diff --git a/src/Geopilot.Frontend/src/App.tsx b/src/Geopilot.Frontend/src/App.tsx new file mode 100644 index 00000000..394c369f --- /dev/null +++ b/src/Geopilot.Frontend/src/App.tsx @@ -0,0 +1,309 @@ +import { PublicClientApplication } from "@azure/msal-browser"; +import { MsalProvider } from "@azure/msal-react"; +import { FC, useEffect, useMemo, useState } from "react"; +import { Alert } from "react-bootstrap"; +import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom"; +import { Snackbar } from "@mui/material"; +import BannerContent from "./BannerContent"; +import Footer from "./Footer.jsx"; +import Home from "./pages/home/Home"; +import Admin from "./pages/admin/Admin"; +import ModalContent from "./ModalContent"; +import "./app.css"; +import { AuthProvider } from "./auth/AuthContext"; +import { AdminTemplate } from "./auth/AdminTemplate"; +import { LoggedOutTemplate } from "./auth/LoggedOutTemplate"; +import { I18nextProvider } from "react-i18next"; +import i18n from "./i18n"; +import { createTheme, ThemeProvider } from "@mui/material/styles"; +import { deDE, enUS, frFR, itIT } from "@mui/material/locale"; +import DeliveryOverview from "./pages/admin/DeliveryOverview"; +import Users from "./pages/admin/Users"; +import Mandates from "./pages/admin/Mandates"; +import Organisations from "./pages/admin/Organisations"; +import { PromptProvider } from "./components/prompt/PromptContext"; +import { Prompt } from "./components/prompt/Prompt"; +import { AlertProvider } from "./components/alert/AlertContext"; +import { AlertBanner } from "./components/alert/AlertBanner"; +import { ClientSettings, Language, ModalContentType } from "./AppInterfaces"; + +export const App: FC = () => { + const [modalContent, setModalContent] = useState(); + const [modalContentType, setModalContentType] = useState(); + const [showModalContent, setShowModalContent] = useState(false); + const [showBannerContent, setShowBannerContent] = useState(false); + const [clientSettings, setClientSettings] = useState(); + const [auth, setAuth] = useState(); + const [backendVersion, setBackendVersion] = useState(""); + const [datenschutzContent, setDatenschutzContent] = useState(""); + const [impressumContent, setImpressumContent] = useState(""); + const [infoHilfeContent, setInfoHilfeContent] = useState(""); + const [bannerContent, setBannerContent] = useState(""); + const [nutzungsbestimmungenContent, setNutzungsbestimmungenContent] = useState(""); + const [quickStartContent, setQuickStartContent] = useState(""); + const [licenseInfo, setLicenseInfo] = useState(null); + const [licenseInfoCustom, setLicenseInfoCustom] = useState(null); + const [alertText, setAlertText] = useState(""); + const [language, setLanguage] = useState("en"); + const [theme, setTheme] = useState({}); + + // Update HTML title property + useEffect(() => { + document.title = clientSettings?.application?.name + " " + backendVersion; + }, [clientSettings, backendVersion]); + + useEffect(() => { + const link = document.querySelector("link[rel=icon]"); + const faviconHref = clientSettings?.application?.favicon; + if (faviconHref) { + link?.setAttribute("href", faviconHref); + } + }, [clientSettings]); + + // Fetch client settings + useEffect(() => { + fetch("client-settings.json") + .then(res => res.headers.get("content-type")?.includes("application/json") && res.json()) + .then(setClientSettings); + }, []); + + useEffect(() => { + fetch("/api/v1/user/auth") + .then(res => res.headers.get("content-type")?.includes("application/json") && res.json()) + .then(setAuth); + }, []); + + useEffect(() => { + fetch("api/v1/version") + .then(res => { + if (res.headers.get("content-type")?.includes("text/plain")) { + return res.text(); + } else { + return ""; + } + }) + .then(version => setBackendVersion(version)); + }, []); + + // Fetch optional custom content + useEffect(() => { + fetch("impressum.md") + .then(res => { + if (res.headers.get("content-type")?.includes("ext/markdown")) { + return res.text(); + } else { + return ""; + } + }) + .then(text => setImpressumContent(text)); + + fetch("datenschutz.md") + .then(res => { + if (res.headers.get("content-type")?.includes("ext/markdown")) { + return res.text(); + } else { + return ""; + } + }) + .then(text => setDatenschutzContent(text)); + + fetch("info-hilfe.md") + .then(res => { + if (res.headers.get("content-type")?.includes("ext/markdown")) { + return res.text(); + } else { + return ""; + } + }) + .then(text => setInfoHilfeContent(text)); + + fetch("nutzungsbestimmungen.md") + .then(res => { + if (res.headers.get("content-type")?.includes("ext/markdown")) { + return res.text(); + } else { + return ""; + } + }) + .then(text => setNutzungsbestimmungenContent(text)); + + fetch("banner.md") + .then(res => { + if (res.headers.get("content-type")?.includes("ext/markdown")) { + return res.text(); + } else { + return ""; + } + }) + .then(text => setBannerContent(text)); + + fetch("quickstart.txt") + .then(res => { + if (res.headers.get("content-type")?.includes("text/plain")) { + return res.text(); + } else { + return ""; + } + }) + .then(text => setQuickStartContent(text)); + + fetch("license.json") + .then(res => res.headers.get("content-type")?.includes("application/json") && res.json()) + .then(json => setLicenseInfo(json)); + + fetch("license.custom.json") + .then(res => res.headers.get("content-type")?.includes("application/json") && res.json()) + .then(json => setLicenseInfoCustom(json)); + }, []); + + useEffect(() => { + let baseTheme = {}; + if (clientSettings && clientSettings.theme) { + baseTheme = clientSettings.theme; + } + + let lng = enUS; + switch (language) { + case "de": + lng = deDE; + break; + case "fr": + lng = frFR; + break; + case "it": + lng = itIT; + break; + case "en": + lng = enUS; + break; + } + setTheme(createTheme(baseTheme, lng)); + }, [clientSettings, language]); + + const openModalContent = (content: string, type: ModalContentType) => { + setModalContent(content); + setModalContentType(type); + setShowModalContent(true); + }; + + const authCache = clientSettings?.authCache; + const msalInstance = useMemo(() => { + if (auth !== undefined && authCache != undefined) { + return new PublicClientApplication({ + auth: auth, + cache: authCache, + }); + } else { + return new PublicClientApplication({ + auth: { + clientId: "", // Replace with your client ID + authority: "", // Replace with your authority URL + redirectUri: window.location.origin, + }, + cache: { + cacheLocation: "localStorage", + storeAuthStateInCookie: false, + }, + }); + } + }, [auth, authCache]); + + useEffect(() => { + const handleLanguageChange = (lng: Language) => { + setLanguage(lng); + }; + + i18n.on("languageChanged", handleLanguageChange); + + return () => { + i18n.off("languageChanged", handleLanguageChange); + }; + }, []); + + return ( + + + + {clientSettings !== undefined && ( + + + + + +
+ + + + + openModalContent(nutzungsbestimmungenContent, "markdown") + } + quickStartContent={quickStartContent} + setShowBannerContent={setShowBannerContent} + /> +
+ + } + /> + + + + } /> + + + + + } /> + }> + } /> + } /> + } /> + } /> + + + + + setShowModalContent(false)} + /> + {bannerContent && showBannerContent && ( + setShowBannerContent(false)} /> + )} +
+ + setAlertText("")}> +

{alertText}

+
+
+
+
+
+ )} +
+
+
+ ); +}; + +export default App; diff --git a/src/Geopilot.Frontend/src/AppInterfaces.ts b/src/Geopilot.Frontend/src/AppInterfaces.ts new file mode 100644 index 00000000..e03af40c --- /dev/null +++ b/src/Geopilot.Frontend/src/AppInterfaces.ts @@ -0,0 +1,44 @@ +import { User } from "./auth/AuthInterfaces"; + +export interface ClientSettings { + authCache: { + cacheLocation: string; + storeAuthStateInCookie: boolean; + }; + authScopes: string[]; + application: { + name: string; + logo: string; + favicon: string; + }; + vendor: { + name: string; + logo: string; + url: string; + }; + theme: object; +} + +export type Language = "de" | "fr" | "it" | "en"; + +export interface TranslationFunction { + (key: string): string; +} + +export interface DataGridColumnValueFormatterParams { + value: string | number; +} + +export type ModalContentType = "markdown" | "raw"; + +export interface Mandate { + name: string; +} + +export interface Delivery { + id: number; + date: Date; + declaringUser: User; + mandate: Mandate; + comment: string; +} diff --git a/src/Geopilot.Frontend/src/Header.jsx b/src/Geopilot.Frontend/src/Header.tsx similarity index 82% rename from src/Geopilot.Frontend/src/Header.jsx rename to src/Geopilot.Frontend/src/Header.tsx index 2c51a5cd..258dd067 100644 --- a/src/Geopilot.Frontend/src/Header.jsx +++ b/src/Geopilot.Frontend/src/Header.tsx @@ -1,6 +1,5 @@ import { useAuth } from "./auth"; import { useTranslation } from "react-i18next"; -import * as React from "react"; import { AppBar, Box, @@ -17,19 +16,27 @@ import { } from "@mui/material"; import MenuIcon from "@mui/icons-material/Menu"; import AccountCircleOutlinedIcon from "@mui/icons-material/AccountCircleOutlined"; -import { LoggedInTemplate } from "./auth/LoggedInTemplate.jsx"; -import { LoggedOutTemplate } from "./auth/LoggedOutTemplate.jsx"; -import { AdminTemplate } from "./auth/AdminTemplate.jsx"; +import { LoggedInTemplate } from "./auth/LoggedInTemplate.js"; +import { LoggedOutTemplate } from "./auth/LoggedOutTemplate.js"; +import { AdminTemplate } from "./auth/AdminTemplate.js"; import { useLocation, useNavigate } from "react-router-dom"; +import { ClientSettings } from "./AppInterfaces"; +import { FC, useState } from "react"; -export const Header = ({ clientSettings, hasDrawerToggle, handleDrawerToggle }) => { +interface HeaderProps { + clientSettings: ClientSettings; + hasDrawerToggle?: boolean; + handleDrawerToggle?: () => void; +} + +export const Header: FC = ({ clientSettings, hasDrawerToggle, handleDrawerToggle }) => { const { user, login, logout } = useAuth(); const { t } = useTranslation(); const navigate = useNavigate(); const location = useLocation(); - const [userMenuOpen, setUserMenuOpen] = React.useState(false); + const [userMenuOpen, setUserMenuOpen] = useState(false); - const toggleUserMenu = newOpen => () => { + const toggleUserMenu = (newOpen: boolean) => () => { setUserMenuOpen(newOpen); }; @@ -59,7 +66,8 @@ export const Header = ({ clientSettings, hasDrawerToggle, handleDrawerToggle }) src={clientSettings?.vendor?.logo} alt={`Logo of ${clientSettings?.vendor?.name}`} onError={e => { - e.target.style.display = "none"; + const img = e.target as HTMLImageElement; + img.style.display = "none"; }} /> @@ -70,7 +78,8 @@ export const Header = ({ clientSettings, hasDrawerToggle, handleDrawerToggle }) src={clientSettings?.vendor?.logo} alt={`Logo of ${clientSettings?.vendor?.name}`} onError={e => { - e.target.style.display = "none"; + const img = e.target as HTMLImageElement; + img.style.display = "none"; }} /> )} @@ -100,8 +109,8 @@ export const Header = ({ clientSettings, hasDrawerToggle, handleDrawerToggle }) onClick={toggleUserMenu(false)} onKeyDown={toggleUserMenu(false)}> - - + + diff --git a/src/Geopilot.Frontend/src/ModalContent.jsx b/src/Geopilot.Frontend/src/ModalContent.jsx index 0331db41..a2886610 100644 --- a/src/Geopilot.Frontend/src/ModalContent.jsx +++ b/src/Geopilot.Frontend/src/ModalContent.jsx @@ -8,7 +8,7 @@ export const ModalContent = props => { const { t } = useTranslation(); return ( - + {type === "markdown" && ( rehypeExternalLinks({ target: "_blank" })]}> diff --git a/src/Geopilot.Frontend/src/auth/AdminTemplate.jsx b/src/Geopilot.Frontend/src/auth/AdminTemplate.jsx deleted file mode 100644 index 47b51fab..00000000 --- a/src/Geopilot.Frontend/src/auth/AdminTemplate.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import { useAuth } from "."; - -export const AdminTemplate = ({ children }) => { - const { user } = useAuth(); - - return user?.isAdmin ? children : null; -}; diff --git a/src/Geopilot.Frontend/src/auth/AdminTemplate.tsx b/src/Geopilot.Frontend/src/auth/AdminTemplate.tsx new file mode 100644 index 00000000..8edad453 --- /dev/null +++ b/src/Geopilot.Frontend/src/auth/AdminTemplate.tsx @@ -0,0 +1,12 @@ +import { useAuth } from "."; +import { FC, ReactNode } from "react"; + +interface AdminTemplateProps { + children: ReactNode; +} + +export const AdminTemplate: FC = ({ children }) => { + const { user } = useAuth(); + + return user?.isAdmin ? children : null; +}; diff --git a/src/Geopilot.Frontend/src/auth/AuthContext.jsx b/src/Geopilot.Frontend/src/auth/AuthContext.tsx similarity index 81% rename from src/Geopilot.Frontend/src/auth/AuthContext.jsx rename to src/Geopilot.Frontend/src/auth/AuthContext.tsx index bbd47bc6..9944fd5f 100644 --- a/src/Geopilot.Frontend/src/auth/AuthContext.jsx +++ b/src/Geopilot.Frontend/src/auth/AuthContext.tsx @@ -1,5 +1,6 @@ import { useMsal } from "@azure/msal-react"; -import { createContext, useCallback, useState, useEffect, useRef } from "react"; +import { createContext, FC, useCallback, useState, useEffect, useRef } from "react"; +import { AuthContextInterface, AuthProviderProps, User } from "./AuthInterfaces"; const authDefault = { user: undefined, @@ -7,24 +8,24 @@ const authDefault = { logout: () => {}, }; -export const AuthContext = createContext(authDefault); +export const AuthContext = createContext(authDefault); -export const AuthProvider = ({ children, authScopes, onLoginError }) => { +export const AuthProvider: FC = ({ children, authScopes, onLoginError }) => { const { instance } = useMsal(); - const [user, setUser] = useState(); - const loginSilentIntervalRef = useRef(); + const [user, setUser] = useState(); + const loginSilentIntervalRef = useRef(); const fetchUserInfo = useCallback(async () => { const userResult = await fetch("/api/v1/user"); if (!userResult.ok) throw new Error(userResult.statusText); const userJson = await userResult.json(); - setUser({ name: userJson.fullName, isAdmin: userJson.isAdmin }); + setUser({ id: userJson.id, fullName: userJson.fullName, isAdmin: userJson.isAdmin, email: userJson.email }); }, [setUser]); const loginCompleted = useCallback( - async idToken => { + async (idToken: string) => { document.cookie = `geopilot.auth=${idToken};Path=/;Secure`; await fetchUserInfo(); }, diff --git a/src/Geopilot.Frontend/src/auth/AuthInterfaces.ts b/src/Geopilot.Frontend/src/auth/AuthInterfaces.ts new file mode 100644 index 00000000..b23f221b --- /dev/null +++ b/src/Geopilot.Frontend/src/auth/AuthInterfaces.ts @@ -0,0 +1,20 @@ +import React, { Dispatch } from "react"; + +export interface User { + id: number; + fullName: string; + isAdmin: boolean; + email: string; +} + +export interface AuthContextInterface { + user: User | undefined; + login: () => void; + logout: () => void; +} + +export interface AuthProviderProps { + children: React.ReactNode; + authScopes: string[]; + onLoginError: Dispatch>; +} diff --git a/src/Geopilot.Frontend/src/auth/LoggedInTemplate.jsx b/src/Geopilot.Frontend/src/auth/LoggedInTemplate.jsx deleted file mode 100644 index a505563e..00000000 --- a/src/Geopilot.Frontend/src/auth/LoggedInTemplate.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import { useAuth } from "."; - -export const LoggedInTemplate = ({ children }) => { - const { user } = useAuth(); - - return user ? children : null; -}; diff --git a/src/Geopilot.Frontend/src/auth/LoggedInTemplate.tsx b/src/Geopilot.Frontend/src/auth/LoggedInTemplate.tsx new file mode 100644 index 00000000..175c818f --- /dev/null +++ b/src/Geopilot.Frontend/src/auth/LoggedInTemplate.tsx @@ -0,0 +1,12 @@ +import { useAuth } from "."; +import { FC, ReactNode } from "react"; + +interface LoggedInTemplateProps { + children: ReactNode; +} + +export const LoggedInTemplate: FC = ({ children }) => { + const { user } = useAuth(); + + return user ? children : null; +}; diff --git a/src/Geopilot.Frontend/src/auth/LoggedOutTemplate.jsx b/src/Geopilot.Frontend/src/auth/LoggedOutTemplate.jsx deleted file mode 100644 index 4822594f..00000000 --- a/src/Geopilot.Frontend/src/auth/LoggedOutTemplate.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import { useAuth } from "."; - -export const LoggedOutTemplate = ({ children }) => { - const { user } = useAuth(); - - return user ? null : children; -}; diff --git a/src/Geopilot.Frontend/src/auth/LoggedOutTemplate.tsx b/src/Geopilot.Frontend/src/auth/LoggedOutTemplate.tsx new file mode 100644 index 00000000..c91bea3e --- /dev/null +++ b/src/Geopilot.Frontend/src/auth/LoggedOutTemplate.tsx @@ -0,0 +1,12 @@ +import { useAuth } from "."; +import { FC, ReactNode } from "react"; + +interface LoggedOutTemplateProps { + children: ReactNode; +} + +export const LoggedOutTemplate: FC = ({ children }) => { + const { user } = useAuth(); + + return user ? null : children; +}; diff --git a/src/Geopilot.Frontend/src/auth/index.js b/src/Geopilot.Frontend/src/auth/index.ts similarity index 65% rename from src/Geopilot.Frontend/src/auth/index.js rename to src/Geopilot.Frontend/src/auth/index.ts index 8cc4eeac..fcb6a50f 100644 --- a/src/Geopilot.Frontend/src/auth/index.js +++ b/src/Geopilot.Frontend/src/auth/index.ts @@ -1,4 +1,4 @@ import { useContext } from "react"; -import { AuthContext } from "./AuthContext"; +import { AuthContext } from "./AuthContext.js"; export const useAuth = () => useContext(AuthContext); diff --git a/src/Geopilot.Frontend/src/components/alert/AlertBanner.tsx b/src/Geopilot.Frontend/src/components/alert/AlertBanner.tsx new file mode 100644 index 00000000..b751087d --- /dev/null +++ b/src/Geopilot.Frontend/src/components/alert/AlertBanner.tsx @@ -0,0 +1,20 @@ +import { useContext } from "react"; +import { AlertContext } from "./AlertContext.js"; +import { Alert, Snackbar } from "@mui/material"; + +export const AlertBanner = () => { + const alertContext = useContext(AlertContext); + return ( + alertContext.alertIsOpen && ( + + + {alertContext.text} + + + ) + ); +}; diff --git a/src/Geopilot.Frontend/src/components/alert/AlertContext.tsx b/src/Geopilot.Frontend/src/components/alert/AlertContext.tsx new file mode 100644 index 00000000..c2648856 --- /dev/null +++ b/src/Geopilot.Frontend/src/components/alert/AlertContext.tsx @@ -0,0 +1,42 @@ +import { createContext, FC, useState } from "react"; +import { AlertOptions, AlertProviderProps, AlertContextInterface } from "./AlertInterfaces"; +import { AlertColor } from "@mui/material"; + +export const AlertContext = createContext({ + alertIsOpen: false, + text: undefined, + severity: undefined, + autoHideDuration: null, + showAlert: () => {}, + closeAlert: () => {}, +}); + +export const AlertProvider: FC = ({ children }) => { + const [alert, setAlert] = useState(null); + + const showAlert = (text: string, severity: AlertColor | undefined, allowAutoHide: boolean | undefined) => { + setAlert({ + text: text, + severity: severity ?? "info", + allowAutoHide: allowAutoHide ?? false, + }); + }; + + const closeAlert = () => { + setAlert(null); + }; + + return ( + + {children} + + ); +}; diff --git a/src/Geopilot.Frontend/src/components/alert/AlertInterfaces.ts b/src/Geopilot.Frontend/src/components/alert/AlertInterfaces.ts new file mode 100644 index 00000000..8f6690b1 --- /dev/null +++ b/src/Geopilot.Frontend/src/components/alert/AlertInterfaces.ts @@ -0,0 +1,21 @@ +import { ReactNode } from "react"; +import { AlertColor } from "@mui/material"; + +export interface AlertContextInterface { + alertIsOpen: boolean; + text: string | undefined; + severity: AlertColor | undefined; + autoHideDuration: number | null; + showAlert: (text: string, severity?: AlertColor, allowAutoHide?: boolean) => void; + closeAlert: () => void; +} + +export interface AlertOptions { + text: string; + severity?: AlertColor; + allowAutoHide?: boolean; +} + +export interface AlertProviderProps { + children: ReactNode; +} diff --git a/src/Geopilot.Frontend/src/components/alert/alertBanner.jsx b/src/Geopilot.Frontend/src/components/alert/alertBanner.jsx deleted file mode 100644 index 84a26dff..00000000 --- a/src/Geopilot.Frontend/src/components/alert/alertBanner.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import { useContext } from "react"; -import { AlertContext } from "./alertContext.jsx"; -import { Alert, Snackbar } from "@mui/material"; - -export const AlertBanner = () => { - const { alertIsOpen, text, severity, autoHideDuration, closeAlert } = useContext(AlertContext); - return ( - alertIsOpen && ( - - - {text} - - - ) - ); -}; diff --git a/src/Geopilot.Frontend/src/components/alert/alertContext.jsx b/src/Geopilot.Frontend/src/components/alert/alertContext.jsx deleted file mode 100644 index 7dd3e31f..00000000 --- a/src/Geopilot.Frontend/src/components/alert/alertContext.jsx +++ /dev/null @@ -1,40 +0,0 @@ -import { createContext, useState } from "react"; - -export const AlertContext = createContext({ - alertIsOpen: false, - text: "", - severity: "", - autoHideDuration: null, - showAlert: () => {}, - closeAlert: () => {}, -}); - -export const AlertProvider = props => { - const [alert, setAlert] = useState(null); - - const showAlert = (text, severity, allowAutoHide) => { - setAlert({ - text: text, - severity: severity ? severity : "info", - autoHideDuration: allowAutoHide === true ? 6000 : null, - }); - }; - - const closeAlert = () => { - setAlert(null); - }; - - return ( - - {props.children} - - ); -}; diff --git a/src/Geopilot.Frontend/src/components/prompt/prompt.jsx b/src/Geopilot.Frontend/src/components/prompt/Prompt.tsx similarity index 87% rename from src/Geopilot.Frontend/src/components/prompt/prompt.jsx rename to src/Geopilot.Frontend/src/components/prompt/Prompt.tsx index 427d6701..c2411ba6 100644 --- a/src/Geopilot.Frontend/src/components/prompt/prompt.jsx +++ b/src/Geopilot.Frontend/src/components/prompt/Prompt.tsx @@ -1,6 +1,6 @@ import { useContext } from "react"; import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle } from "@mui/material"; -import { PromptContext } from "./promptContext"; +import { PromptContext } from "./PromptContext"; export const Prompt = () => { const { promptIsOpen, title, message, actions, closePrompt } = useContext(PromptContext); @@ -15,9 +15,7 @@ export const Prompt = () => {