diff --git a/client/index.html b/client/index.html index b52bcf3..a51df8c 100644 --- a/client/index.html +++ b/client/index.html @@ -2,7 +2,7 @@ - + lango-client diff --git a/client/package-lock.json b/client/package-lock.json index dd712fc..5401921 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -19,6 +19,7 @@ "react-intl": "^7.1.14", "react-redux": "^9.2.0", "react-router-dom": "^7.9.5", + "recharts": "^3.6.0", "zod": "^4.1.13" }, "devDependencies": { @@ -140,7 +141,6 @@ "version": "7.28.5", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1540,6 +1540,7 @@ "version": "2.0.5", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cacheable/utils": "^2.3.0", "@keyv/bigmap": "^1.1.0", @@ -1551,6 +1552,7 @@ "version": "1.3.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "hashery": "^1.2.0", "hookified": "^1.13.0" @@ -1575,6 +1577,7 @@ "version": "2.3.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "hashery": "^1.2.0", "keyv": "^5.5.4" @@ -1584,6 +1587,7 @@ "version": "5.5.4", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@keyv/serialize": "^1.1.1" } @@ -1687,7 +1691,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -1727,7 +1730,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -1746,6 +1748,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -1768,6 +1771,7 @@ } ], "license": "MIT-0", + "peer": true, "engines": { "node": ">=18" }, @@ -1779,6 +1783,7 @@ "version": "4.2.1", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/JounQin" @@ -2782,6 +2787,7 @@ "version": "0.3.11", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -2804,7 +2810,8 @@ "node_modules/@keyv/serialize": { "version": "1.1.1", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@mdx-js/react": { "version": "3.1.1", @@ -2880,6 +2887,7 @@ "version": "2.1.5", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -2892,6 +2900,7 @@ "version": "2.0.5", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 8" } @@ -2900,6 +2909,7 @@ "version": "1.2.8", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -3895,7 +3905,8 @@ "node_modules/@types/aria-query": { "version": "5.0.4", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -3943,6 +3954,69 @@ "assertion-error": "^2.0.1" } }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, "node_modules/@types/deep-eql": { "version": "4.0.2", "dev": true, @@ -4055,7 +4129,6 @@ "node_modules/@types/react": { "version": "19.2.7", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -4064,7 +4137,6 @@ "version": "19.2.3", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -4133,7 +4205,6 @@ "version": "8.48.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.48.0", "@typescript-eslint/types": "8.48.0", @@ -4490,7 +4561,6 @@ "version": "4.0.14", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/utils": "4.0.14", "fflate": "^0.8.2", @@ -4523,7 +4593,6 @@ "version": "8.15.0", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4651,6 +4720,7 @@ "version": "2.1.0", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -4809,6 +4879,7 @@ "version": "2.0.0", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -4958,7 +5029,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -4993,12 +5063,14 @@ "node_modules/buffer-from": { "version": "1.1.2", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/cacheable": { "version": "2.2.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cacheable/memory": "^2.0.5", "@cacheable/utils": "^2.3.0", @@ -5011,6 +5083,7 @@ "version": "5.5.4", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@keyv/serialize": "^1.1.1" } @@ -5317,7 +5390,8 @@ "node_modules/colord": { "version": "2.9.3", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/colorette": { "version": "2.0.20", @@ -5385,6 +5459,7 @@ "version": "9.0.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -5423,6 +5498,7 @@ "version": "3.2.3", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12 || >=16" } @@ -5472,6 +5548,127 @@ "version": "3.2.3", "license": "MIT" }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/data-urls": { "version": "6.0.0", "dev": true, @@ -5552,6 +5749,12 @@ "version": "10.6.0", "license": "MIT" }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, "node_modules/deep-eql": { "version": "5.0.2", "dev": true, @@ -5628,6 +5831,7 @@ "version": "3.0.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "path-type": "^4.0.0" }, @@ -5649,7 +5853,8 @@ "node_modules/dom-accessibility-api": { "version": "0.5.16", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/dunder-proto": { "version": "1.0.1", @@ -5701,6 +5906,7 @@ "version": "2.2.1", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -5720,6 +5926,7 @@ "version": "1.3.4", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "is-arrayish": "^0.2.1" } @@ -5886,12 +6093,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-toolkit": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.43.0.tgz", + "integrity": "sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/esbuild": { "version": "0.25.12", "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -5950,7 +6166,6 @@ "version": "9.39.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -6417,7 +6632,6 @@ }, "node_modules/eventemitter3": { "version": "5.0.1", - "dev": true, "license": "MIT" }, "node_modules/expect": { @@ -6453,6 +6667,7 @@ "version": "3.3.3", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -6468,6 +6683,7 @@ "version": "5.1.2", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -6498,12 +6714,14 @@ "url": "https://opencollective.com/fastify" } ], - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/fastest-levenshtein": { "version": "1.0.16", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 4.9.1" } @@ -6512,6 +6730,7 @@ "version": "1.19.1", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "reusify": "^1.0.4" } @@ -6827,6 +7046,7 @@ "version": "2.0.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "global-prefix": "^3.0.0" }, @@ -6838,6 +7058,7 @@ "version": "3.0.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ini": "^1.3.5", "kind-of": "^6.0.2", @@ -6851,6 +7072,7 @@ "version": "1.3.1", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "isexe": "^2.0.0" }, @@ -6888,6 +7110,7 @@ "version": "11.1.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -6907,6 +7130,7 @@ "version": "5.3.2", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 4" } @@ -6914,7 +7138,8 @@ "node_modules/globjoin": { "version": "0.1.4", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/globrex": { "version": "0.1.2", @@ -7020,6 +7245,7 @@ "version": "1.2.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "hookified": "^1.13.0" }, @@ -7073,7 +7299,8 @@ "node_modules/hookified": { "version": "1.13.0", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", @@ -7095,6 +7322,7 @@ "version": "3.3.1", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" }, @@ -7206,7 +7434,8 @@ "node_modules/ini": { "version": "1.3.8", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/internal-slot": { "version": "1.1.0", @@ -7221,6 +7450,15 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/intl-messageformat": { "version": "10.7.18", "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.18.tgz", @@ -7252,7 +7490,8 @@ "node_modules/is-arrayish": { "version": "0.2.1", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/is-async-function": { "version": "2.1.1", @@ -7889,7 +8128,6 @@ "version": "27.2.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@acemir/cssom": "^0.9.23", "@asamuzakjp/dom-selector": "^6.7.4", @@ -7943,7 +8181,8 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -8003,6 +8242,7 @@ "version": "6.0.3", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -8010,7 +8250,8 @@ "node_modules/known-css-properties": { "version": "0.37.0", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/levn": { "version": "0.4.1", @@ -8027,7 +8268,8 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lint-staged": { "version": "16.2.7", @@ -8143,7 +8385,8 @@ "node_modules/lodash.truncate": { "version": "4.4.2", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/log-update": { "version": "6.1.0", @@ -8239,6 +8482,7 @@ "version": "1.5.0", "dev": true, "license": "MIT", + "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -8286,6 +8530,7 @@ "version": "2.1.3", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -8311,6 +8556,7 @@ "version": "1.4.1", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 8" } @@ -8422,7 +8668,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@inquirer/confirm": "^5.0.0", "@mswjs/interceptors": "^0.40.0", @@ -8517,6 +8762,7 @@ "version": "3.0.0", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -8736,6 +8982,7 @@ "version": "5.2.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -8810,6 +9057,7 @@ "version": "4.0.0", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -8880,7 +9128,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -8918,6 +9165,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18.0" }, @@ -8954,7 +9202,6 @@ "version": "7.1.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -8988,7 +9235,6 @@ "version": "3.6.2", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -9003,6 +9249,7 @@ "version": "27.5.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -9016,6 +9263,7 @@ "version": "5.2.0", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -9054,6 +9302,7 @@ "version": "0.5.2", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "hookified": "^1.13.0" }, @@ -9078,12 +9327,12 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react": { "version": "19.2.0", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -9119,7 +9368,6 @@ "node_modules/react-dom": { "version": "19.2.0", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -9130,7 +9378,6 @@ "node_modules/react-hook-form": { "version": "7.68.0", "license": "MIT", - "peer": true, "engines": { "node": ">=18.0.0" }, @@ -9169,13 +9416,12 @@ }, "node_modules/react-is": { "version": "17.0.2", - "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-redux": { "version": "9.2.0", "license": "MIT", - "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -9263,6 +9509,46 @@ "node": ">= 4" } }, + "node_modules/recharts": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.6.0.tgz", + "integrity": "sha512-L5bjxvQRAe26RlToBAziKUB7whaGKEwD3znoM6fz3DrTowCIC/FnJYnuq1GEzB8Zv2kdTfaxQfi5GoH0tBinyg==", + "license": "MIT", + "workspaces": [ + "www" + ], + "dependencies": { + "@reduxjs/toolkit": "1.x.x || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^10.1.1", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.1.1", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts/node_modules/immer": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/redent": { "version": "3.0.0", "dev": true, @@ -9288,8 +9574,7 @@ }, "node_modules/redux": { "version": "5.0.1", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -9462,6 +9747,7 @@ "version": "1.1.0", "dev": true, "license": "MIT", + "peer": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -9476,7 +9762,6 @@ "version": "4.53.3", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -9531,6 +9816,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "queue-microtask": "^1.2.2" } @@ -9593,7 +9879,6 @@ "version": "1.94.2", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -9852,6 +10137,7 @@ "version": "0.5.21", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -9910,7 +10196,6 @@ "version": "10.1.2", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.0", @@ -10381,17 +10666,20 @@ "node_modules/stylelint/node_modules/balanced-match": { "version": "2.0.0", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/stylelint/node_modules/emoji-regex": { "version": "8.0.0", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/stylelint/node_modules/file-entry-cache": { "version": "11.1.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "flat-cache": "^6.1.19" } @@ -10400,6 +10688,7 @@ "version": "6.1.19", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cacheable": "^2.2.0", "flatted": "^3.3.3", @@ -10410,6 +10699,7 @@ "version": "3.0.0", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -10418,6 +10708,7 @@ "version": "5.0.0", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -10426,6 +10717,7 @@ "version": "4.2.3", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -10439,6 +10731,7 @@ "version": "6.0.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -10461,6 +10754,7 @@ "version": "3.2.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" @@ -10485,7 +10779,8 @@ }, "node_modules/svg-tags": { "version": "1.0.0", - "dev": true + "dev": true, + "peer": true }, "node_modules/symbol-tree": { "version": "3.2.4", @@ -10501,6 +10796,7 @@ "version": "6.9.0", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", @@ -10516,6 +10812,7 @@ "version": "8.17.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -10530,12 +10827,14 @@ "node_modules/table/node_modules/emoji-regex": { "version": "8.0.0", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/table/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -10543,12 +10842,14 @@ "node_modules/table/node_modules/json-schema-traverse": { "version": "1.0.0", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/table/node_modules/slice-ansi": { "version": "4.0.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -10565,6 +10866,7 @@ "version": "4.2.3", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -10578,6 +10880,7 @@ "version": "6.0.1", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -10617,11 +10920,11 @@ "node_modules/terser/node_modules/commander": { "version": "2.20.3", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tiny-invariant": { "version": "1.3.3", - "dev": true, "license": "MIT" }, "node_modules/tinybench": { @@ -10876,7 +11179,6 @@ "version": "5.9.3", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11044,11 +11346,32 @@ "dev": true, "license": "MIT" }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/vite": { "version": "7.2.4", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -11149,7 +11472,6 @@ "version": "4.0.14", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/expect": "4.0.14", "@vitest/mocker": "4.0.14", @@ -11545,6 +11867,7 @@ "version": "5.0.1", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" @@ -11697,7 +12020,6 @@ "node_modules/zod": { "version": "4.1.13", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/client/package.json b/client/package.json index e1555c8..124a0d3 100644 --- a/client/package.json +++ b/client/package.json @@ -30,10 +30,11 @@ "clsx": "^2.1.1", "react": "^19.1.1", "react-dom": "^19.1.1", - "react-intl": "^7.1.14", "react-hook-form": "^7.68.0", + "react-intl": "^7.1.14", "react-redux": "^9.2.0", "react-router-dom": "^7.9.5", + "recharts": "^3.6.0", "zod": "^4.1.13" }, "devDependencies": { diff --git a/client/public/mockServiceWorker.js b/client/public/mockServiceWorker.js deleted file mode 100644 index 6951ed1..0000000 --- a/client/public/mockServiceWorker.js +++ /dev/null @@ -1,349 +0,0 @@ -/* eslint-disable */ -/* tslint:disable */ - -/** - * Mock Service Worker. - * @see https://github.com/mswjs/msw - * - Please do NOT modify this file. - */ - -const PACKAGE_VERSION = '2.12.3' -const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82' -const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') -const activeClientIds = new Set() - -addEventListener('install', function () { - self.skipWaiting() -}) - -addEventListener('activate', function (event) { - event.waitUntil(self.clients.claim()) -}) - -addEventListener('message', async function (event) { - const clientId = Reflect.get(event.source || {}, 'id') - - if (!clientId || !self.clients) { - return - } - - const client = await self.clients.get(clientId) - - if (!client) { - return - } - - const allClients = await self.clients.matchAll({ - type: 'window', - }) - - switch (event.data) { - case 'KEEPALIVE_REQUEST': { - sendToClient(client, { - type: 'KEEPALIVE_RESPONSE', - }) - break - } - - case 'INTEGRITY_CHECK_REQUEST': { - sendToClient(client, { - type: 'INTEGRITY_CHECK_RESPONSE', - payload: { - packageVersion: PACKAGE_VERSION, - checksum: INTEGRITY_CHECKSUM, - }, - }) - break - } - - case 'MOCK_ACTIVATE': { - activeClientIds.add(clientId) - - sendToClient(client, { - type: 'MOCKING_ENABLED', - payload: { - client: { - id: client.id, - frameType: client.frameType, - }, - }, - }) - break - } - - case 'CLIENT_CLOSED': { - activeClientIds.delete(clientId) - - const remainingClients = allClients.filter((client) => { - return client.id !== clientId - }) - - // Unregister itself when there are no more clients - if (remainingClients.length === 0) { - self.registration.unregister() - } - - break - } - } -}) - -addEventListener('fetch', function (event) { - const requestInterceptedAt = Date.now() - - // Bypass navigation requests. - if (event.request.mode === 'navigate') { - return - } - - // Opening the DevTools triggers the "only-if-cached" request - // that cannot be handled by the worker. Bypass such requests. - if ( - event.request.cache === 'only-if-cached' && - event.request.mode !== 'same-origin' - ) { - return - } - - // Bypass all requests when there are no active clients. - // Prevents the self-unregistered worked from handling requests - // after it's been terminated (still remains active until the next reload). - if (activeClientIds.size === 0) { - return - } - - const requestId = crypto.randomUUID() - event.respondWith(handleRequest(event, requestId, requestInterceptedAt)) -}) - -/** - * @param {FetchEvent} event - * @param {string} requestId - * @param {number} requestInterceptedAt - */ -async function handleRequest(event, requestId, requestInterceptedAt) { - const client = await resolveMainClient(event) - const requestCloneForEvents = event.request.clone() - const response = await getResponse( - event, - client, - requestId, - requestInterceptedAt, - ) - - // Send back the response clone for the "response:*" life-cycle events. - // Ensure MSW is active and ready to handle the message, otherwise - // this message will pend indefinitely. - if (client && activeClientIds.has(client.id)) { - const serializedRequest = await serializeRequest(requestCloneForEvents) - - // Clone the response so both the client and the library could consume it. - const responseClone = response.clone() - - sendToClient( - client, - { - type: 'RESPONSE', - payload: { - isMockedResponse: IS_MOCKED_RESPONSE in response, - request: { - id: requestId, - ...serializedRequest, - }, - response: { - type: responseClone.type, - status: responseClone.status, - statusText: responseClone.statusText, - headers: Object.fromEntries(responseClone.headers.entries()), - body: responseClone.body, - }, - }, - }, - responseClone.body ? [serializedRequest.body, responseClone.body] : [], - ) - } - - return response -} - -/** - * Resolve the main client for the given event. - * Client that issues a request doesn't necessarily equal the client - * that registered the worker. It's with the latter the worker should - * communicate with during the response resolving phase. - * @param {FetchEvent} event - * @returns {Promise} - */ -async function resolveMainClient(event) { - const client = await self.clients.get(event.clientId) - - if (activeClientIds.has(event.clientId)) { - return client - } - - if (client?.frameType === 'top-level') { - return client - } - - const allClients = await self.clients.matchAll({ - type: 'window', - }) - - return allClients - .filter((client) => { - // Get only those clients that are currently visible. - return client.visibilityState === 'visible' - }) - .find((client) => { - // Find the client ID that's recorded in the - // set of clients that have registered the worker. - return activeClientIds.has(client.id) - }) -} - -/** - * @param {FetchEvent} event - * @param {Client | undefined} client - * @param {string} requestId - * @param {number} requestInterceptedAt - * @returns {Promise} - */ -async function getResponse(event, client, requestId, requestInterceptedAt) { - // Clone the request because it might've been already used - // (i.e. its body has been read and sent to the client). - const requestClone = event.request.clone() - - function passthrough() { - // Cast the request headers to a new Headers instance - // so the headers can be manipulated with. - const headers = new Headers(requestClone.headers) - - // Remove the "accept" header value that marked this request as passthrough. - // This prevents request alteration and also keeps it compliant with the - // user-defined CORS policies. - const acceptHeader = headers.get('accept') - if (acceptHeader) { - const values = acceptHeader.split(',').map((value) => value.trim()) - const filteredValues = values.filter( - (value) => value !== 'msw/passthrough', - ) - - if (filteredValues.length > 0) { - headers.set('accept', filteredValues.join(', ')) - } else { - headers.delete('accept') - } - } - - return fetch(requestClone, { headers }) - } - - // Bypass mocking when the client is not active. - if (!client) { - return passthrough() - } - - // Bypass initial page load requests (i.e. static assets). - // The absence of the immediate/parent client in the map of the active clients - // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet - // and is not ready to handle requests. - if (!activeClientIds.has(client.id)) { - return passthrough() - } - - // Notify the client that a request has been intercepted. - const serializedRequest = await serializeRequest(event.request) - const clientMessage = await sendToClient( - client, - { - type: 'REQUEST', - payload: { - id: requestId, - interceptedAt: requestInterceptedAt, - ...serializedRequest, - }, - }, - [serializedRequest.body], - ) - - switch (clientMessage.type) { - case 'MOCK_RESPONSE': { - return respondWithMock(clientMessage.data) - } - - case 'PASSTHROUGH': { - return passthrough() - } - } - - return passthrough() -} - -/** - * @param {Client} client - * @param {any} message - * @param {Array} transferrables - * @returns {Promise} - */ -function sendToClient(client, message, transferrables = []) { - return new Promise((resolve, reject) => { - const channel = new MessageChannel() - - channel.port1.onmessage = (event) => { - if (event.data && event.data.error) { - return reject(event.data.error) - } - - resolve(event.data) - } - - client.postMessage(message, [ - channel.port2, - ...transferrables.filter(Boolean), - ]) - }) -} - -/** - * @param {Response} response - * @returns {Response} - */ -function respondWithMock(response) { - // Setting response status code to 0 is a no-op. - // However, when responding with a "Response.error()", the produced Response - // instance will have status code set to 0. Since it's not possible to create - // a Response instance with status code 0, handle that use-case separately. - if (response.status === 0) { - return Response.error() - } - - const mockedResponse = new Response(response.body, response) - - Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { - value: true, - enumerable: true, - }) - - return mockedResponse -} - -/** - * @param {Request} request - */ -async function serializeRequest(request) { - return { - url: request.url, - mode: request.mode, - method: request.method, - headers: Object.fromEntries(request.headers.entries()), - cache: request.cache, - credentials: request.credentials, - destination: request.destination, - integrity: request.integrity, - redirect: request.redirect, - referrer: request.referrer, - referrerPolicy: request.referrerPolicy, - body: await request.arrayBuffer(), - keepalive: request.keepalive, - } -} diff --git a/client/public/scripts/mockServiceWorker.js b/client/public/scripts/mockServiceWorker.js deleted file mode 100644 index 6951ed1..0000000 --- a/client/public/scripts/mockServiceWorker.js +++ /dev/null @@ -1,349 +0,0 @@ -/* eslint-disable */ -/* tslint:disable */ - -/** - * Mock Service Worker. - * @see https://github.com/mswjs/msw - * - Please do NOT modify this file. - */ - -const PACKAGE_VERSION = '2.12.3' -const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82' -const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') -const activeClientIds = new Set() - -addEventListener('install', function () { - self.skipWaiting() -}) - -addEventListener('activate', function (event) { - event.waitUntil(self.clients.claim()) -}) - -addEventListener('message', async function (event) { - const clientId = Reflect.get(event.source || {}, 'id') - - if (!clientId || !self.clients) { - return - } - - const client = await self.clients.get(clientId) - - if (!client) { - return - } - - const allClients = await self.clients.matchAll({ - type: 'window', - }) - - switch (event.data) { - case 'KEEPALIVE_REQUEST': { - sendToClient(client, { - type: 'KEEPALIVE_RESPONSE', - }) - break - } - - case 'INTEGRITY_CHECK_REQUEST': { - sendToClient(client, { - type: 'INTEGRITY_CHECK_RESPONSE', - payload: { - packageVersion: PACKAGE_VERSION, - checksum: INTEGRITY_CHECKSUM, - }, - }) - break - } - - case 'MOCK_ACTIVATE': { - activeClientIds.add(clientId) - - sendToClient(client, { - type: 'MOCKING_ENABLED', - payload: { - client: { - id: client.id, - frameType: client.frameType, - }, - }, - }) - break - } - - case 'CLIENT_CLOSED': { - activeClientIds.delete(clientId) - - const remainingClients = allClients.filter((client) => { - return client.id !== clientId - }) - - // Unregister itself when there are no more clients - if (remainingClients.length === 0) { - self.registration.unregister() - } - - break - } - } -}) - -addEventListener('fetch', function (event) { - const requestInterceptedAt = Date.now() - - // Bypass navigation requests. - if (event.request.mode === 'navigate') { - return - } - - // Opening the DevTools triggers the "only-if-cached" request - // that cannot be handled by the worker. Bypass such requests. - if ( - event.request.cache === 'only-if-cached' && - event.request.mode !== 'same-origin' - ) { - return - } - - // Bypass all requests when there are no active clients. - // Prevents the self-unregistered worked from handling requests - // after it's been terminated (still remains active until the next reload). - if (activeClientIds.size === 0) { - return - } - - const requestId = crypto.randomUUID() - event.respondWith(handleRequest(event, requestId, requestInterceptedAt)) -}) - -/** - * @param {FetchEvent} event - * @param {string} requestId - * @param {number} requestInterceptedAt - */ -async function handleRequest(event, requestId, requestInterceptedAt) { - const client = await resolveMainClient(event) - const requestCloneForEvents = event.request.clone() - const response = await getResponse( - event, - client, - requestId, - requestInterceptedAt, - ) - - // Send back the response clone for the "response:*" life-cycle events. - // Ensure MSW is active and ready to handle the message, otherwise - // this message will pend indefinitely. - if (client && activeClientIds.has(client.id)) { - const serializedRequest = await serializeRequest(requestCloneForEvents) - - // Clone the response so both the client and the library could consume it. - const responseClone = response.clone() - - sendToClient( - client, - { - type: 'RESPONSE', - payload: { - isMockedResponse: IS_MOCKED_RESPONSE in response, - request: { - id: requestId, - ...serializedRequest, - }, - response: { - type: responseClone.type, - status: responseClone.status, - statusText: responseClone.statusText, - headers: Object.fromEntries(responseClone.headers.entries()), - body: responseClone.body, - }, - }, - }, - responseClone.body ? [serializedRequest.body, responseClone.body] : [], - ) - } - - return response -} - -/** - * Resolve the main client for the given event. - * Client that issues a request doesn't necessarily equal the client - * that registered the worker. It's with the latter the worker should - * communicate with during the response resolving phase. - * @param {FetchEvent} event - * @returns {Promise} - */ -async function resolveMainClient(event) { - const client = await self.clients.get(event.clientId) - - if (activeClientIds.has(event.clientId)) { - return client - } - - if (client?.frameType === 'top-level') { - return client - } - - const allClients = await self.clients.matchAll({ - type: 'window', - }) - - return allClients - .filter((client) => { - // Get only those clients that are currently visible. - return client.visibilityState === 'visible' - }) - .find((client) => { - // Find the client ID that's recorded in the - // set of clients that have registered the worker. - return activeClientIds.has(client.id) - }) -} - -/** - * @param {FetchEvent} event - * @param {Client | undefined} client - * @param {string} requestId - * @param {number} requestInterceptedAt - * @returns {Promise} - */ -async function getResponse(event, client, requestId, requestInterceptedAt) { - // Clone the request because it might've been already used - // (i.e. its body has been read and sent to the client). - const requestClone = event.request.clone() - - function passthrough() { - // Cast the request headers to a new Headers instance - // so the headers can be manipulated with. - const headers = new Headers(requestClone.headers) - - // Remove the "accept" header value that marked this request as passthrough. - // This prevents request alteration and also keeps it compliant with the - // user-defined CORS policies. - const acceptHeader = headers.get('accept') - if (acceptHeader) { - const values = acceptHeader.split(',').map((value) => value.trim()) - const filteredValues = values.filter( - (value) => value !== 'msw/passthrough', - ) - - if (filteredValues.length > 0) { - headers.set('accept', filteredValues.join(', ')) - } else { - headers.delete('accept') - } - } - - return fetch(requestClone, { headers }) - } - - // Bypass mocking when the client is not active. - if (!client) { - return passthrough() - } - - // Bypass initial page load requests (i.e. static assets). - // The absence of the immediate/parent client in the map of the active clients - // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet - // and is not ready to handle requests. - if (!activeClientIds.has(client.id)) { - return passthrough() - } - - // Notify the client that a request has been intercepted. - const serializedRequest = await serializeRequest(event.request) - const clientMessage = await sendToClient( - client, - { - type: 'REQUEST', - payload: { - id: requestId, - interceptedAt: requestInterceptedAt, - ...serializedRequest, - }, - }, - [serializedRequest.body], - ) - - switch (clientMessage.type) { - case 'MOCK_RESPONSE': { - return respondWithMock(clientMessage.data) - } - - case 'PASSTHROUGH': { - return passthrough() - } - } - - return passthrough() -} - -/** - * @param {Client} client - * @param {any} message - * @param {Array} transferrables - * @returns {Promise} - */ -function sendToClient(client, message, transferrables = []) { - return new Promise((resolve, reject) => { - const channel = new MessageChannel() - - channel.port1.onmessage = (event) => { - if (event.data && event.data.error) { - return reject(event.data.error) - } - - resolve(event.data) - } - - client.postMessage(message, [ - channel.port2, - ...transferrables.filter(Boolean), - ]) - }) -} - -/** - * @param {Response} response - * @returns {Response} - */ -function respondWithMock(response) { - // Setting response status code to 0 is a no-op. - // However, when responding with a "Response.error()", the produced Response - // instance will have status code set to 0. Since it's not possible to create - // a Response instance with status code 0, handle that use-case separately. - if (response.status === 0) { - return Response.error() - } - - const mockedResponse = new Response(response.body, response) - - Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { - value: true, - enumerable: true, - }) - - return mockedResponse -} - -/** - * @param {Request} request - */ -async function serializeRequest(request) { - return { - url: request.url, - mode: request.mode, - method: request.method, - headers: Object.fromEntries(request.headers.entries()), - cache: request.cache, - credentials: request.credentials, - destination: request.destination, - integrity: request.integrity, - redirect: request.redirect, - referrer: request.referrer, - referrerPolicy: request.referrerPolicy, - body: await request.arrayBuffer(), - keepalive: request.keepalive, - } -} diff --git a/client/public/vite.svg b/client/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/client/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/client/src/app/providers/router/ui/MainLayout/Layout.module.scss b/client/src/app/providers/router/ui/MainLayout/Layout.module.scss index 729cbb2..f96a0b9 100644 --- a/client/src/app/providers/router/ui/MainLayout/Layout.module.scss +++ b/client/src/app/providers/router/ui/MainLayout/Layout.module.scss @@ -1,8 +1,8 @@ .layout { display: grid; - grid-template-columns: 280px auto; - gap: 20px; - margin: 20px; height: var(--layout-height); + margin: 20px; + gap: 20px; + grid-template-columns: 280px auto; } diff --git a/client/src/app/providers/router/ui/WithoutAside/WithoutAside.module.scss b/client/src/app/providers/router/ui/WithoutAside/WithoutAside.module.scss index aa0936a..61319e8 100644 --- a/client/src/app/providers/router/ui/WithoutAside/WithoutAside.module.scss +++ b/client/src/app/providers/router/ui/WithoutAside/WithoutAside.module.scss @@ -1,6 +1,6 @@ .layout { - margin: 20px; - height: var(--layout-height); + + margin: 20px; } diff --git a/client/src/app/styles/index.scss b/client/src/app/styles/index.scss index 76aa8b4..5a90a56 100644 --- a/client/src/app/styles/index.scss +++ b/client/src/app/styles/index.scss @@ -1,3 +1,3 @@ -@use "./scss/reset.scss"; -@use "./scss/vars.scss"; -@use "./scss/base.scss"; +@use "./scss/reset"; +@use "./scss/vars"; +@use "./scss/base"; diff --git a/client/src/app/styles/scss/base.scss b/client/src/app/styles/scss/base.scss index 48bf466..17a6a65 100644 --- a/client/src/app/styles/scss/base.scss +++ b/client/src/app/styles/scss/base.scss @@ -5,9 +5,9 @@ } body { + overflow: hidden; width: 100%; height: 100vh; - color: var(--color-text); background: conic-gradient( from 330deg at 50% 50%, var(--grad-1) 0deg, @@ -16,10 +16,10 @@ body { var(--grad-4) 270deg, var(--grad-1) 360deg ); + background-blend-mode: screen; background-size: 100% 100%; - background-blend-mode: screen; - overflow: hidden; + color: var(--color-text); font: var(--font-m); } diff --git a/client/src/app/styles/scss/fonts.scss b/client/src/app/styles/scss/fonts.scss index 1d7d8e8..6847d96 100644 --- a/client/src/app/styles/scss/fonts.scss +++ b/client/src/app/styles/scss/fonts.scss @@ -1,21 +1,27 @@ @font-face { - font-family: "Manrope-Custom"; - src: url("/fonts/Manrope-Medium.ttf") format("truetype"); - font-weight: 500; + font-family: Manrope-Custom; font-style: normal; + font-weight: 500; + src: url('../../../shared/assets/fonts/Manrope-Medium.ttf') format('truetype'); } @font-face { - font-family: "Manrope-Custom"; - src: url("/fonts/Manrope-Regular.ttf") format("truetype"); - font-weight: 400; + font-family: Manrope-Custom; font-style: normal; + font-weight: 400; + src: url('../../../shared/assets/fonts/Manrope-Regular.ttf') format('truetype'); } @font-face { - font-family: "Manrope-Custom"; - src: url("/fonts/Manrope-SemiBold.ttf") format("truetype"); - font-weight: 600; + font-family: Manrope-Custom; font-style: normal; + font-weight: 600; + src: url('../../../shared/assets/fonts/Manrope-SemiBold.ttf') format('truetype'); } +@font-face { + font-family: Manrope-Custom; + font-style: normal; + font-weight: 700; + src: url('../../../shared/assets/fonts/Manrope-Bold.ttf') format('truetype'); +} diff --git a/client/src/app/styles/scss/vars.scss b/client/src/app/styles/scss/vars.scss index 0bf3498..5108e56 100644 --- a/client/src/app/styles/scss/vars.scss +++ b/client/src/app/styles/scss/vars.scss @@ -1,17 +1,15 @@ -@use './fonts.scss'; +@use './fonts'; :root { + /* Шрифты */ --family-main: 'Manrope-Custom'; - --font-size-m: 16px; --font-line-m: 24px; --font-m: 16px / 24px var(--family-main); - --font-size-l: 24px; --font-line-l: 32px; --font-l: 24px / 32px var(--family-main); - --font-size-xl: 32px; --font-line-xl: 40px; --font-xl: 32px / 40px var(--family-main); @@ -25,7 +23,7 @@ --z-index-modal: 10; /* Цвета */ - --overlay: rgba(0, 0, 0, 0.5); + --overlay: rgba(0 0 0 / 0.5); --red-light: #ff0000; --red-dark: #c50a0a; --color-error: #ff0000; @@ -38,6 +36,6 @@ /* Модальное окно */ - --modal-background: #fff; + --modal-background: #ffffff; } diff --git a/client/src/app/styles/themes/index.scss b/client/src/app/styles/themes/index.scss index f95ef86..23db001 100644 --- a/client/src/app/styles/themes/index.scss +++ b/client/src/app/styles/themes/index.scss @@ -2,15 +2,12 @@ --color-primary-dark: #0f4c81; --color-primary: #2a73ff; --color-primary-light: #dde8ff; - --grad-1: #e8f1ff; --grad-2: #c9ddff; --grad-3: #8bb2ff; --grad-4: #3b6ec8; - --color-background: #eaf6ff; --color-section: #ffffff; - --color-title: #0a3d62; --color-text: #4a4a4a; --color-active-link: #dde8ff; @@ -20,15 +17,12 @@ --color-primary: #6366f1; --color-primary-light: #a5b4fc; --color-primary-dark: #4338ca; - --color-background: #0f172a; --color-section: #1e293b; - --grad-1: #1e293b; --grad-2: #334155; --grad-3: #475569; --grad-4: #64748b; - --color-title: #e2e8f0; --color-text: #cbd5e1; --color-active-link: #4338ca; diff --git a/client/src/features/auth/ui/LoginForm/LoginForm.module.scss b/client/src/features/auth/ui/LoginForm/LoginForm.module.scss deleted file mode 100644 index e69de29..0000000 diff --git a/client/src/features/auth/ui/RegisterForm/RegisterForm.module.scss b/client/src/features/auth/ui/RegisterForm/RegisterForm.module.scss deleted file mode 100644 index e69de29..0000000 diff --git a/client/src/features/auth/ui/RegisterForm/RegisterForm.tsx b/client/src/features/auth/ui/RegisterForm/RegisterForm.tsx index 64e3fef..a96ac02 100644 --- a/client/src/features/auth/ui/RegisterForm/RegisterForm.tsx +++ b/client/src/features/auth/ui/RegisterForm/RegisterForm.tsx @@ -3,8 +3,6 @@ import { memo } from 'react'; import { useRegisterForm } from '@/features/auth/model/hooks/useRegisterForm.ts'; import { Button, Checkbox, Form, Input, Typography } from '@/shared/ui'; -import styles from './RegisterForm.module.scss'; - interface RegisterFormProps { onSwap: () => void; } @@ -17,7 +15,7 @@ export const RegisterForm = memo(({ onSwap }: RegisterFormProps) => { } = form; return ( -
+ Registration diff --git a/client/src/features/journey/ui/Journey.module.scss b/client/src/features/journey/ui/Journey.module.scss deleted file mode 100644 index e69de29..0000000 diff --git a/client/src/features/journey/ui/Journey.tsx b/client/src/features/journey/ui/Journey.tsx index 28c8257..7a241f1 100644 --- a/client/src/features/journey/ui/Journey.tsx +++ b/client/src/features/journey/ui/Journey.tsx @@ -1,9 +1,5 @@ import { memo } from 'react'; -interface JourneyProps {} - -export const Journey = memo((props: JourneyProps) => { - const {} = props; - +export const Journey = memo(() => { return
Journey
; }); diff --git a/client/src/pages/AuthPage/ui/AuthPage/AuthPage.module.scss b/client/src/pages/AuthPage/ui/AuthPage/AuthPage.module.scss index 152a731..00df22f 100644 --- a/client/src/pages/AuthPage/ui/AuthPage/AuthPage.module.scss +++ b/client/src/pages/AuthPage/ui/AuthPage/AuthPage.module.scss @@ -1,7 +1,7 @@ .auth { display: flex; + width: 100%; + min-height: 100vh; align-items: center; justify-content: center; - min-height: 100vh; - width: 100%; } \ No newline at end of file diff --git a/client/src/pages/ErrorPage/ui/ErrorPage/ErrorPage.module.scss b/client/src/pages/ErrorPage/ui/ErrorPage/ErrorPage.module.scss deleted file mode 100644 index c3089c5..0000000 --- a/client/src/pages/ErrorPage/ui/ErrorPage/ErrorPage.module.scss +++ /dev/null @@ -1,3 +0,0 @@ -.error{ - -} \ No newline at end of file diff --git a/client/src/pages/ErrorPage/ui/ErrorPage/ErrorPage.tsx b/client/src/pages/ErrorPage/ui/ErrorPage/ErrorPage.tsx index 0c02989..01b8d05 100644 --- a/client/src/pages/ErrorPage/ui/ErrorPage/ErrorPage.tsx +++ b/client/src/pages/ErrorPage/ui/ErrorPage/ErrorPage.tsx @@ -1,7 +1,5 @@ -import styles from './ErrorPage.module.scss'; - const ErrorPage = () => { - return
404
; + return
404
; }; export default ErrorPage; diff --git a/client/src/pages/LoaderPage/ui/LoaderPage/LoaderPage.module.scss b/client/src/pages/LoaderPage/ui/LoaderPage/LoaderPage.module.scss index f23834b..b8ba6ef 100644 --- a/client/src/pages/LoaderPage/ui/LoaderPage/LoaderPage.module.scss +++ b/client/src/pages/LoaderPage/ui/LoaderPage/LoaderPage.module.scss @@ -1,6 +1,6 @@ .page { - width: 100%; display: flex; + width: 100%; align-items: center; justify-content: center; } diff --git a/client/src/pages/MainPage/ui/DashboardPage/DashboardPage.module.scss b/client/src/pages/MainPage/ui/DashboardPage/DashboardPage.module.scss index 9c47cf5..c13e17f 100644 --- a/client/src/pages/MainPage/ui/DashboardPage/DashboardPage.module.scss +++ b/client/src/pages/MainPage/ui/DashboardPage/DashboardPage.module.scss @@ -1,12 +1,40 @@ .main { - font-size: 40px; - font-weight: 700; + display: grid; + justify-content: space-between; color: var(--color-title); + gap: 20px; + grid-template-columns: auto 320px; +} - width: 100%; - height: 100%; +.block { padding: 20px; border-radius: 12px; background-color: var(--color-section); } + +.title { + margin-bottom: 12px; +} + +.list { + margin-bottom: 12px; +} + +.user_list { + display: flex; + flex-direction: column; + gap: 12px; +} + +.user { + display: flex; + align-items: center; + gap: 12px; +} + +.avatar { + width: 32px; + height: 32px; + border-radius: 50%; +} diff --git a/client/src/pages/MainPage/ui/DashboardPage/DashboardPage.tsx b/client/src/pages/MainPage/ui/DashboardPage/DashboardPage.tsx index b4b9579..75b061a 100644 --- a/client/src/pages/MainPage/ui/DashboardPage/DashboardPage.tsx +++ b/client/src/pages/MainPage/ui/DashboardPage/DashboardPage.tsx @@ -1,12 +1,58 @@ import { SECTION_TITLE_ID } from '@/shared/const/section'; -import { Page } from '@/shared/ui'; +import { Button, Page, Typography } from '@/shared/ui'; import styles from './DashboardPage.module.scss'; +const mockImages = [ + 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTIJxOLfDct_vEPdS-6OsRnS-kDl_HCv5nI2A&s', + `https://st3.depositphotos.com/3557671/12561/v/450/ + depositphotos_125614312-stock-illustration-woman-icon-cartoon-single-avatarpeaople.jpg`, + 'https://www.svgrepo.com/show/382097/female-avatar-girl-face-woman-user-9.svg', +]; + const DashboardPage = () => { return ( -
Dashboard
+
+
+ + Ежедневные задания: + +
    +
  • 1. Выучи 5 новых слов
  • +
  • 2. Пройди 3 теста
  • +
  • 3. Повтори слова из вчерашн...
  • +
  • 4. Поделись прогрессом с дру...
  • +
+ +
+
+
+ + Список лидеров: + +
    +
  • + avatar image + + Mike Stupid + +
  • +
  • + avatar image + + Alice girl + +
  • +
  • + avatar image + + Terminato 1C + +
  • +
+
+
); }; diff --git a/client/public/fonts/Manrope-Bold.ttf b/client/src/shared/assets/fonts/Manrope-Bold.ttf similarity index 100% rename from client/public/fonts/Manrope-Bold.ttf rename to client/src/shared/assets/fonts/Manrope-Bold.ttf diff --git a/client/public/fonts/Manrope-ExtraBold.ttf b/client/src/shared/assets/fonts/Manrope-ExtraBold.ttf similarity index 100% rename from client/public/fonts/Manrope-ExtraBold.ttf rename to client/src/shared/assets/fonts/Manrope-ExtraBold.ttf diff --git a/client/public/fonts/Manrope-ExtraLight.ttf b/client/src/shared/assets/fonts/Manrope-ExtraLight.ttf similarity index 100% rename from client/public/fonts/Manrope-ExtraLight.ttf rename to client/src/shared/assets/fonts/Manrope-ExtraLight.ttf diff --git a/client/public/fonts/Manrope-Light.ttf b/client/src/shared/assets/fonts/Manrope-Light.ttf similarity index 100% rename from client/public/fonts/Manrope-Light.ttf rename to client/src/shared/assets/fonts/Manrope-Light.ttf diff --git a/client/public/fonts/Manrope-Medium.ttf b/client/src/shared/assets/fonts/Manrope-Medium.ttf similarity index 100% rename from client/public/fonts/Manrope-Medium.ttf rename to client/src/shared/assets/fonts/Manrope-Medium.ttf diff --git a/client/public/fonts/Manrope-Regular.ttf b/client/src/shared/assets/fonts/Manrope-Regular.ttf similarity index 100% rename from client/public/fonts/Manrope-Regular.ttf rename to client/src/shared/assets/fonts/Manrope-Regular.ttf diff --git a/client/public/fonts/Manrope-SemiBold.ttf b/client/src/shared/assets/fonts/Manrope-SemiBold.ttf similarity index 100% rename from client/public/fonts/Manrope-SemiBold.ttf rename to client/src/shared/assets/fonts/Manrope-SemiBold.ttf diff --git a/client/src/shared/config/storybook/styles/index.scss b/client/src/shared/config/storybook/styles/index.scss index 7fb861a..6f03dfb 100644 --- a/client/src/shared/config/storybook/styles/index.scss +++ b/client/src/shared/config/storybook/styles/index.scss @@ -1,5 +1,5 @@ *{ - width: #000; + width: #000000; } .title { @@ -8,7 +8,7 @@ font-weight: 600; } -//------Typography--- +// ------Typography--- .typography_inner { color: #ffffff; @@ -34,6 +34,7 @@ } // ------Icons--- + .icons_wrapper { display: grid; gap: 20px; diff --git a/client/src/shared/ui/Button/Button.module.scss b/client/src/shared/ui/Button/Button.module.scss index e76e61e..6a30de2 100644 --- a/client/src/shared/ui/Button/Button.module.scss +++ b/client/src/shared/ui/Button/Button.module.scss @@ -2,10 +2,10 @@ display: flex; width: 100%; align-items: center; - color: var(--color-title); padding: 6px 12px; border-radius: 4px; + color: var(--color-title); cursor: pointer; font-size: 1rem; gap: 8px; @@ -19,9 +19,11 @@ .start { justify-content: flex-start; } + .center { justify-content: center; } + .end { justify-content: flex-end; } diff --git a/client/src/shared/ui/Checkbox/Checkbox.module.scss b/client/src/shared/ui/Checkbox/Checkbox.module.scss index 52b0395..6208666 100644 --- a/client/src/shared/ui/Checkbox/Checkbox.module.scss +++ b/client/src/shared/ui/Checkbox/Checkbox.module.scss @@ -1,54 +1,16 @@ -.wrapper { - display: flex; - align-items: center; - cursor: pointer; - position: relative; - gap: 0.25rem; - - input[type="checkbox"] { - position: absolute; - opacity: 0; - height: 0; - width: 0; - cursor: pointer; - } - - input[type="checkbox"]:checked + .checkmark { - border-color: var(--color-primary); - background-color: var(--color-primary); - } - - input[type="checkbox"]:checked + .checkmark::after { - display: block; - } - - - &.disabled { - cursor: not-allowed; - - .checkmark { - background-color: var(--color-primary-light); - } - - .text { - color: rgba(0, 0, 0, 0.25); - } - } -} - .checkmark { + position: relative; display: inline-block; border-radius: 5px; background-color: var(--color-background-checkbox); transition: all 0.3s; - position: relative; - &::after { - content: ""; + &:after { position: absolute; display: none; border: solid var(--color-primary-light); border-width: 0 2px 2px 0; + content: ''; transform: rotate(45deg); } @@ -56,9 +18,9 @@ width: 14px; height: 14px; - &::after { + &:after { + top: 0; left: 3px; - top: 0px; width: 4px; height: 8px; } @@ -68,9 +30,9 @@ width: 18px; height: 18px; - &::after { - left: 5px; + &:after { top: 1px; + left: 5px; width: 5px; height: 10px; } @@ -80,9 +42,9 @@ width: 24px; height: 24px; - &::after { - left: 7px; + &:after { top: 1px; + left: 7px; width: 7px; height: 14px; } @@ -91,4 +53,43 @@ .text { margin-left: 5px; -} \ No newline at end of file +} + + +.wrapper { + position: relative; + display: flex; + align-items: center; + cursor: pointer; + gap: 0.25rem; + + &.disabled { + cursor: not-allowed; + + .checkmark { + background-color: var(--color-primary-light); + } + + .text { + color: rgba(0 0 0 / 0.25); + } + } + + input[type='checkbox'] { + position: absolute; + width: 0; + height: 0; + cursor: pointer; + opacity: 0; + } + + input[type='checkbox']:checked + .checkmark { + border-color: var(--color-primary); + background-color: var(--color-primary); + } + + input[type='checkbox']:checked + .checkmark:after { + display: block; + } +} + diff --git a/client/src/shared/ui/Form/Form.module.scss b/client/src/shared/ui/Form/Form.module.scss index 3957f04..d50a9ec 100644 --- a/client/src/shared/ui/Form/Form.module.scss +++ b/client/src/shared/ui/Form/Form.module.scss @@ -1,11 +1,11 @@ .primary { - padding: 24px; - background-color: var(--modal-background); display: flex; - flex-direction: column; - gap: 36px; + width: 100%; max-width: 400px; - width: 100%; + flex-direction: column; + padding: 24px; + background-color: var(--modal-background); + gap: 36px; } \ No newline at end of file diff --git a/client/src/shared/ui/Input/Input.module.scss b/client/src/shared/ui/Input/Input.module.scss index 6251878..c5fac67 100644 --- a/client/src/shared/ui/Input/Input.module.scss +++ b/client/src/shared/ui/Input/Input.module.scss @@ -1,14 +1,14 @@ -.inputWrapper { +.input_wrapper { display: flex; flex-direction: column; gap: 4px; } .label { + color: #000000; font: var(--font-m); font-size: 14px; line-height: 20px; - color: #000; } .input { @@ -36,14 +36,14 @@ .input:focus { border-color: #2563eb; - box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.2); + box-shadow: 0 0 0 2px rgba(37 99 235 / 0.2); } .input:disabled { + border-color: #e5e7eb; background: #f3f4f6; color: #9ca3af; - border-color: #e5e7eb; cursor: not-allowed; } diff --git a/client/src/shared/ui/Input/Input.tsx b/client/src/shared/ui/Input/Input.tsx index 5f66445..ce02aeb 100644 --- a/client/src/shared/ui/Input/Input.tsx +++ b/client/src/shared/ui/Input/Input.tsx @@ -16,7 +16,7 @@ export const Input = memo((props: InputProps) => { const id = useId(); return ( -
+
{label && (