Switch to Axios, Panel component test
This commit is contained in:
@@ -1,28 +1,33 @@
|
|||||||
import js from '@eslint/js'
|
import js from "@eslint/js";
|
||||||
import globals from 'globals'
|
import pluginQuery from "@tanstack/eslint-plugin-query";
|
||||||
import reactHooks from 'eslint-plugin-react-hooks'
|
import reactHooks from "eslint-plugin-react-hooks";
|
||||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
import reactRefresh from "eslint-plugin-react-refresh";
|
||||||
import tseslint from 'typescript-eslint'
|
import globals from "globals";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
export default tseslint.config(
|
export default tseslint.config(
|
||||||
{ ignores: ['dist'] },
|
{ ignores: ["dist"] },
|
||||||
{
|
{
|
||||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
extends: [
|
||||||
files: ['**/*.{ts,tsx}'],
|
js.configs.recommended,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
...pluginQuery.configs["flat/recommended"],
|
||||||
|
],
|
||||||
|
files: ["**/*.{ts,tsx}"],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
ecmaVersion: 2020,
|
ecmaVersion: 2020,
|
||||||
globals: globals.browser,
|
globals: globals.browser,
|
||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
'react-hooks': reactHooks,
|
"react-hooks": reactHooks,
|
||||||
'react-refresh': reactRefresh,
|
"react-refresh": reactRefresh,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
...reactHooks.configs.recommended.rules,
|
...reactHooks.configs.recommended.rules,
|
||||||
'react-refresh/only-export-components': [
|
"react-refresh/only-export-components": [
|
||||||
'warn',
|
"warn",
|
||||||
{ allowConstantExport: true },
|
{ allowConstantExport: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
)
|
);
|
||||||
|
|||||||
75
frontend/package-lock.json
generated
75
frontend/package-lock.json
generated
@@ -10,7 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/react-query": "^5.74.4",
|
"@tanstack/react-query": "^5.74.4",
|
||||||
"@tanstack/react-query-devtools": "^5.74.6",
|
"@tanstack/react-query-devtools": "^5.74.6",
|
||||||
"dompurify": "^3.2.5",
|
"axios": "^1.9.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-router-dom": "^7.5.1",
|
"react-router-dom": "^7.5.1",
|
||||||
@@ -2921,13 +2921,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/trusted-types": {
|
|
||||||
"version": "2.0.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
|
||||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"node_modules/@types/yargs": {
|
"node_modules/@types/yargs": {
|
||||||
"version": "17.0.33",
|
"version": "17.0.33",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
|
||||||
@@ -3334,9 +3327,19 @@
|
|||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
|
||||||
|
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.6",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/babel-jest": {
|
"node_modules/babel-jest": {
|
||||||
"version": "29.7.0",
|
"version": "29.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
|
||||||
@@ -3560,7 +3563,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
@@ -3734,7 +3736,6 @@
|
|||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"delayed-stream": "~1.0.0"
|
"delayed-stream": "~1.0.0"
|
||||||
@@ -3953,7 +3954,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
@@ -4034,15 +4034,6 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dompurify": {
|
|
||||||
"version": "3.2.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.5.tgz",
|
|
||||||
"integrity": "sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ==",
|
|
||||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
|
||||||
"optionalDependencies": {
|
|
||||||
"@types/trusted-types": "^2.0.7"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/dotenv": {
|
"node_modules/dotenv": {
|
||||||
"version": "16.5.0",
|
"version": "16.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz",
|
||||||
@@ -4060,7 +4051,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind-apply-helpers": "^1.0.1",
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
@@ -4155,7 +4145,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -4165,7 +4154,6 @@
|
|||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -4175,7 +4163,6 @@
|
|||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0"
|
"es-errors": "^1.3.0"
|
||||||
@@ -4188,7 +4175,6 @@
|
|||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
@@ -4736,11 +4722,30 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||||
|
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/form-data": {
|
"node_modules/form-data": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
||||||
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asynckit": "^0.4.0",
|
"asynckit": "^0.4.0",
|
||||||
@@ -4778,7 +4783,6 @@
|
|||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
@@ -4808,7 +4812,6 @@
|
|||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind-apply-helpers": "^1.0.2",
|
"call-bind-apply-helpers": "^1.0.2",
|
||||||
@@ -4843,7 +4846,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dunder-proto": "^1.0.1",
|
"dunder-proto": "^1.0.1",
|
||||||
@@ -4942,7 +4944,6 @@
|
|||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -4986,7 +4987,6 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -4999,7 +4999,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-symbols": "^1.0.3"
|
"has-symbols": "^1.0.3"
|
||||||
@@ -5015,7 +5014,6 @@
|
|||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
@@ -6734,7 +6732,6 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -6789,7 +6786,6 @@
|
|||||||
"version": "1.52.0",
|
"version": "1.52.0",
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
@@ -6799,7 +6795,6 @@
|
|||||||
"version": "2.1.35",
|
"version": "2.1.35",
|
||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mime-db": "1.52.0"
|
"mime-db": "1.52.0"
|
||||||
@@ -7434,6 +7429,12 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/prr": {
|
"node_modules/prr": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/react-query": "^5.74.4",
|
"@tanstack/react-query": "^5.74.4",
|
||||||
"@tanstack/react-query-devtools": "^5.74.6",
|
"@tanstack/react-query-devtools": "^5.74.6",
|
||||||
"dompurify": "^3.2.5",
|
"axios": "^1.9.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-router-dom": "^7.5.1",
|
"react-router-dom": "^7.5.1",
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import ArticleSelector from "./components/ArticleSelector/ArticleSelector";
|
|||||||
import Panel from "./components/Panel/Panel";
|
import Panel from "./components/Panel/Panel";
|
||||||
import TOC from "./components/TOC/TOC";
|
import TOC from "./components/TOC/TOC";
|
||||||
|
|
||||||
import useNavState from "./store/navStore";
|
|
||||||
import useUIStore from "./store/uiStore";
|
import useUIStore from "./store/uiStore";
|
||||||
|
|
||||||
import styles from "./App.module.css";
|
import styles from "./App.module.css";
|
||||||
@@ -13,7 +12,6 @@ import { useTOC } from "./hooks/toc";
|
|||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const { numPanels, addPanel } = useUIStore();
|
const { numPanels, addPanel } = useUIStore();
|
||||||
const { celexId, articleId } = useNavState();
|
|
||||||
const { data: toc, isPending, error } = useTOC();
|
const { data: toc, isPending, error } = useTOC();
|
||||||
|
|
||||||
if (isPending) {
|
if (isPending) {
|
||||||
@@ -35,11 +33,9 @@ function App() {
|
|||||||
{Array.from({ length: numPanels }, (_, index) => (
|
{Array.from({ length: numPanels }, (_, index) => (
|
||||||
<Panel
|
<Panel
|
||||||
key={index}
|
key={index}
|
||||||
celexId={celexId!}
|
|
||||||
language={
|
language={
|
||||||
Object.values(Language)[index % Object.values(Language).length]
|
Object.values(Language)[index % Object.values(Language).length]
|
||||||
}
|
}
|
||||||
articleId={articleId!}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
128
frontend/src/components/Panel/Panel.test.tsx
Normal file
128
frontend/src/components/Panel/Panel.test.tsx
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
import { fireEvent, render } from "@testing-library/react";
|
||||||
|
import { getArticle } from "../../lib/api";
|
||||||
|
import { Language } from "../../lib/types";
|
||||||
|
import useNavState from "../../store/navStore";
|
||||||
|
import useUIStore from "../../store/uiStore";
|
||||||
|
import Panel from "./Panel";
|
||||||
|
|
||||||
|
jest.mock("../../store/uiStore");
|
||||||
|
jest.mock("../../store/navStore");
|
||||||
|
jest.mock("../../lib/api");
|
||||||
|
jest.mock("../../constants", () =>
|
||||||
|
Promise.resolve({
|
||||||
|
API_URL: "http://localhost:8000/api", // Mock the API_URL to a local server for testing
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const queryClient = new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
retry: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const renderWithClient = (ui: React.ReactElement) => {
|
||||||
|
const { rerender, ...result } = render(
|
||||||
|
<QueryClientProvider client={queryClient}>{ui}</QueryClientProvider>
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
rerender: (rerenderUi: React.ReactElement) =>
|
||||||
|
rerender(
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
{rerenderUi}
|
||||||
|
</QueryClientProvider>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = ({ children }) => (
|
||||||
|
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
describe("Panel Component", () => {
|
||||||
|
const mockSetSelectedParagraphId = jest.fn();
|
||||||
|
const mockUseUIStore = {
|
||||||
|
selectedParagraphId: null,
|
||||||
|
setSelectedParagraphId: mockSetSelectedParagraphId,
|
||||||
|
};
|
||||||
|
const mockNavState = {
|
||||||
|
celexId: "123",
|
||||||
|
articleId: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.mocked(useNavState).mockReturnValue(mockNavState);
|
||||||
|
jest.mocked(useUIStore).mockReturnValue(mockUseUIStore);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
queryClient.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders loading state", () => {
|
||||||
|
(getArticle as jest.Mock).mockReturnValue(new Promise(() => {}));
|
||||||
|
const { getByText } = render(<Panel />, { wrapper });
|
||||||
|
expect(getByText("Loading...")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders error state", async () => {
|
||||||
|
(getArticle as jest.Mock).mockRejectedValue(new Error("Failed to fetch"));
|
||||||
|
const { findByText } = renderWithClient(<Panel />);
|
||||||
|
expect(
|
||||||
|
await findByText("An error has occurred: Failed to fetch")
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders article content", async () => {
|
||||||
|
const mockData = `<div class='paragraph' data-paragraph-id='1'>Test Content</div>`;
|
||||||
|
jest.mocked(getArticle).mockResolvedValue(mockData);
|
||||||
|
|
||||||
|
const result = renderWithClient(<Panel />);
|
||||||
|
expect(await result.findByText("Test Content")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("highlights a paragraph on click", async () => {
|
||||||
|
const mockData = `
|
||||||
|
<div class='paragraph' data-paragraph-id='1'>Paragraph 1</div>
|
||||||
|
<div class='paragraph' data-paragraph-id='2'>Paragraph 2</div>
|
||||||
|
`;
|
||||||
|
(getArticle as jest.Mock).mockResolvedValue(mockData);
|
||||||
|
|
||||||
|
const result = renderWithClient(<Panel />);
|
||||||
|
|
||||||
|
const paragraph1 = await result.findByText("Paragraph 1");
|
||||||
|
const paragraph2 = await result.findByText("Paragraph 2");
|
||||||
|
|
||||||
|
fireEvent.click(paragraph1);
|
||||||
|
expect(paragraph1.classList.contains("highlight")).toBe(true);
|
||||||
|
expect(paragraph2.classList.contains("highlight")).toBe(false);
|
||||||
|
expect(mockSetSelectedParagraphId).toHaveBeenCalledWith("1");
|
||||||
|
|
||||||
|
fireEvent.click(paragraph2);
|
||||||
|
expect(paragraph1.classList.contains("highlight")).toBe(false);
|
||||||
|
expect(paragraph2.classList.contains("highlight")).toBe(true);
|
||||||
|
expect(mockSetSelectedParagraphId).toHaveBeenCalledWith("2");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders LanguageSwitcher and updates language", async () => {
|
||||||
|
jest
|
||||||
|
.mocked(getArticle)
|
||||||
|
.mockResolvedValue(
|
||||||
|
"<div class='paragraph' data-paragraph-id='1'>Test Content</div>"
|
||||||
|
);
|
||||||
|
const result = renderWithClient(<Panel language={Language.FRA} />);
|
||||||
|
|
||||||
|
const languageSwitcher = await result.findByRole("combobox");
|
||||||
|
expect(languageSwitcher).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.change(languageSwitcher, { target: { value: Language.ENG } });
|
||||||
|
expect(jest.mocked(getArticle)).toHaveBeenCalledWith(
|
||||||
|
"123",
|
||||||
|
1,
|
||||||
|
Language.ENG
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,31 +1,25 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
|
||||||
import DOMPurify from "dompurify";
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
import { getArticle } from "../../lib/api";
|
|
||||||
import { Language } from "../../lib/types";
|
import { Language } from "../../lib/types";
|
||||||
import useUIStore from "../../store/uiStore";
|
import useUIStore from "../../store/uiStore";
|
||||||
import LanguageSwitcher from "../LanguageSwitcher/LanguageSwitcher";
|
import LanguageSwitcher from "../LanguageSwitcher/LanguageSwitcher";
|
||||||
|
|
||||||
|
import { useArticle } from "../../hooks/useArticle";
|
||||||
|
import useNavState from "../../store/navStore";
|
||||||
import "../../styles/PanelContent.css";
|
import "../../styles/PanelContent.css";
|
||||||
import styles from "./Panel.module.css";
|
import styles from "./Panel.module.css";
|
||||||
|
|
||||||
type PanelProps = {
|
type PanelProps = {
|
||||||
celexId: string;
|
|
||||||
language?: Language;
|
language?: Language;
|
||||||
articleId: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function Panel({ celexId, language, articleId }: PanelProps) {
|
function Panel({ language }: PanelProps) {
|
||||||
const { selectedParagraphId, setSelectedParagraphId } = useUIStore();
|
const { selectedParagraphId, setSelectedParagraphId } = useUIStore();
|
||||||
|
|
||||||
const [lang, setLang] = useState(language || Language.ENG);
|
const [lang, setLang] = useState(language || Language.ENG);
|
||||||
const articleRef = useRef<HTMLDivElement>(null);
|
const articleRef = useRef<HTMLDivElement>(null);
|
||||||
const { data, isPending, error } = useQuery({
|
const { articleId, celexId } = useNavState();
|
||||||
queryKey: ["article", celexId, articleId, lang],
|
const { data, isPending, error } = useArticle(celexId, articleId, lang);
|
||||||
queryFn: () => getArticle(celexId, articleId, lang),
|
|
||||||
enabled: !!celexId && !!articleId,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const articleElement = articleRef.current;
|
const articleElement = articleRef.current;
|
||||||
@@ -85,7 +79,7 @@ function Panel({ celexId, language, articleId }: PanelProps) {
|
|||||||
<div
|
<div
|
||||||
ref={articleRef}
|
ref={articleRef}
|
||||||
lang={lang.substring(0, 2)}
|
lang={lang.substring(0, 2)}
|
||||||
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(data) || "" }}
|
dangerouslySetInnerHTML={{ __html: data }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
3
frontend/src/constants.ts
Normal file
3
frontend/src/constants.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
const { VITE_API_URL: API_URL } = import.meta.env;
|
||||||
|
|
||||||
|
export { API_URL };
|
||||||
15
frontend/src/hooks/useArticle.ts
Normal file
15
frontend/src/hooks/useArticle.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { getArticle } from "../lib/api";
|
||||||
|
import { Language } from "../lib/types";
|
||||||
|
|
||||||
|
export const useArticle = (
|
||||||
|
celexId: string | null,
|
||||||
|
articleId: number | null,
|
||||||
|
lang: Language
|
||||||
|
) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["article", celexId, articleId, lang],
|
||||||
|
queryFn: () => getArticle(celexId!, articleId!, lang),
|
||||||
|
enabled: !!celexId && !!articleId,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,6 +1,14 @@
|
|||||||
|
import Axios from "axios";
|
||||||
|
import { API_URL } from "../constants";
|
||||||
import { Division, Language } from "./types";
|
import { Division, Language } from "./types";
|
||||||
|
|
||||||
const API_URL = import.meta.env.VITE_API_URL;
|
const axios = Axios.create({
|
||||||
|
baseURL: API_URL,
|
||||||
|
timeout: 5000,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
async function getArticle(
|
async function getArticle(
|
||||||
celexId: string,
|
celexId: string,
|
||||||
@@ -10,16 +18,16 @@ async function getArticle(
|
|||||||
console.debug(
|
console.debug(
|
||||||
`Fetching article ${article} for CELEX ID ${celexId} in ${language} language`
|
`Fetching article ${article} for CELEX ID ${celexId} in ${language} language`
|
||||||
);
|
);
|
||||||
const response = await fetch(
|
const response = await axios.get<string>(
|
||||||
`${API_URL}/${celexId}/articles/${article}/${language}`
|
`${celexId}/articles/${article}/${language}`
|
||||||
);
|
);
|
||||||
return await response.text();
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getArticleIds(celexId: string): Promise<number[]> {
|
async function getArticleIds(celexId: string): Promise<number[]> {
|
||||||
console.debug(`Fetching article list for CELEX ID ${celexId}`);
|
console.debug(`Fetching article list for CELEX ID ${celexId}`);
|
||||||
const response = await fetch(`${API_URL}/${celexId}/articles`);
|
const response = await axios.get<number[]>(`${celexId}/articles`);
|
||||||
return await response.json();
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getToc(
|
async function getToc(
|
||||||
@@ -27,8 +35,8 @@ async function getToc(
|
|||||||
language: Language
|
language: Language
|
||||||
): Promise<Division[]> {
|
): Promise<Division[]> {
|
||||||
console.debug(`Fetching TOC for CELEX ID ${celexId}`);
|
console.debug(`Fetching TOC for CELEX ID ${celexId}`);
|
||||||
const response = await fetch(`${API_URL}/${celexId}/toc/${language}`);
|
const response = await axios.get<Division[]>(`${celexId}/toc/${language}`);
|
||||||
return (await response.json()) as Division[];
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getArticle, getArticleIds, getToc };
|
export { getArticle, getArticleIds, getToc };
|
||||||
|
|||||||
Reference in New Issue
Block a user