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 (
-