feat(frontend): add authorization
This commit is contained in:
parent
ee7be12428
commit
2c1a8ea127
@ -1,7 +1,9 @@
|
||||
import pluginQuery from '@tanstack/eslint-plugin-query';
|
||||
// @ts-expect-error fix package
|
||||
import config from 'eslint-config-ksv741';
|
||||
|
||||
export default [
|
||||
...pluginQuery.configs['flat/recommended'],
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
...config,
|
||||
{
|
||||
@ -10,6 +12,74 @@ export default [
|
||||
devDependencies: ['eslint.config.ts', 'rsbuild.config.ts'],
|
||||
}],
|
||||
'import/no-anonymous-default-export': 'off',
|
||||
'import/order': [
|
||||
'error', {
|
||||
pathGroups: [
|
||||
{
|
||||
pattern: '__spec__/**',
|
||||
group: 'builtin',
|
||||
position: 'before',
|
||||
},
|
||||
{
|
||||
pattern: 'apps/**',
|
||||
group: 'internal',
|
||||
position: 'after',
|
||||
},
|
||||
{
|
||||
pattern: 'pages/**',
|
||||
group: 'internal',
|
||||
position: 'after',
|
||||
},
|
||||
{
|
||||
pattern: 'widgets/**',
|
||||
group: 'internal',
|
||||
position: 'after',
|
||||
},
|
||||
{
|
||||
pattern: 'features/**',
|
||||
group: 'internal',
|
||||
position: 'after',
|
||||
},
|
||||
{
|
||||
pattern: 'entities/**',
|
||||
group: 'internal',
|
||||
position: 'after',
|
||||
},
|
||||
{
|
||||
pattern: 'shared/**',
|
||||
group: 'internal',
|
||||
position: 'after',
|
||||
},
|
||||
],
|
||||
distinctGroup: true,
|
||||
groups: [
|
||||
'builtin',
|
||||
'external',
|
||||
'internal',
|
||||
'parent',
|
||||
'sibling',
|
||||
'object',
|
||||
'index',
|
||||
'type',
|
||||
],
|
||||
'newlines-between': 'always',
|
||||
alphabetize: {
|
||||
order: 'asc',
|
||||
caseInsensitive: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/strict-boolean-expressions': 'off',
|
||||
'@typescript-eslint/no-floating-promises': 'off',
|
||||
'@typescript-eslint/consistent-return': 'off',
|
||||
'func-style': 'off',
|
||||
'@typescript-eslint/no-confusing-void-expression': 'off',
|
||||
'@typescript-eslint/await-thenable': 'off',
|
||||
'@typescript-eslint/no-misused-promises': 'off',
|
||||
'@typescript-eslint/prefer-nullish-coalescing': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
ignores: ['./src/shared/api/schema.ts'],
|
||||
},
|
||||
];
|
||||
|
||||
448
frontend/package-lock.json
generated
448
frontend/package-lock.json
generated
@ -8,19 +8,24 @@
|
||||
"name": "Investments Control Frontend",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "5.85.5",
|
||||
"axios": "1.11.0",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1"
|
||||
"react-dom": "19.1.1",
|
||||
"react-router": "7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rsbuild/core": "1.4.15",
|
||||
"@rsbuild/plugin-eslint": "1.1.2",
|
||||
"@rsbuild/plugin-react": "1.3.5",
|
||||
"@rsbuild/plugin-type-check": "1.2.4",
|
||||
"@tanstack/eslint-plugin-query": "5.83.1",
|
||||
"@types/react": "19.1.9",
|
||||
"@types/react-dom": "19.1.7",
|
||||
"@typescript-eslint/parser": "8.39.0",
|
||||
"eslint": "9.33.0",
|
||||
"eslint-config-ksv741": "0.2.0",
|
||||
"openapi-typescript": "7.9.1",
|
||||
"typescript": "5.9.2"
|
||||
}
|
||||
},
|
||||
@ -559,6 +564,82 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@redocly/ajv": {
|
||||
"version": "8.11.3",
|
||||
"resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.3.tgz",
|
||||
"integrity": "sha512-4P3iZse91TkBiY+Dx5DUgxQ9GXkVJf++cmI0MOyLDxV9b5MUBI4II6ES8zA5JCbO72nKAJxWrw4PUPW+YP3ZDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
"require-from-string": "^2.0.2",
|
||||
"uri-js-replace": "^1.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/@redocly/ajv/node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@redocly/config": {
|
||||
"version": "0.22.2",
|
||||
"resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.22.2.tgz",
|
||||
"integrity": "sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@redocly/openapi-core": {
|
||||
"version": "1.34.5",
|
||||
"resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.34.5.tgz",
|
||||
"integrity": "sha512-0EbE8LRbkogtcCXU7liAyC00n9uNG9hJ+eMyHFdUsy9lB/WGqnEBgwjA9q2cyzAVcdTkQqTBBU1XePNnN3OijA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@redocly/ajv": "^8.11.2",
|
||||
"@redocly/config": "^0.22.0",
|
||||
"colorette": "^1.2.0",
|
||||
"https-proxy-agent": "^7.0.5",
|
||||
"js-levenshtein": "^1.1.6",
|
||||
"js-yaml": "^4.1.0",
|
||||
"minimatch": "^5.0.1",
|
||||
"pluralize": "^8.0.0",
|
||||
"yaml-ast-parser": "0.0.43"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.17.0",
|
||||
"npm": ">=9.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redocly/openapi-core/node_modules/brace-expansion": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redocly/openapi-core/node_modules/minimatch": {
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
||||
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@rsbuild/core": {
|
||||
"version": "1.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-1.4.15.tgz",
|
||||
@ -903,6 +984,49 @@
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/eslint-plugin-query": {
|
||||
"version": "5.83.1",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.83.1.tgz",
|
||||
"integrity": "sha512-tdkpPFfzkTksN9BIlT/qjixSAtKrsW6PUVRwdKWaOcag7DrD1vpki3UzzdfMQGDRGeg1Ue1Dg+rcl5FJGembNg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/utils": "^8.37.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/query-core": {
|
||||
"version": "5.85.5",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.85.5.tgz",
|
||||
"integrity": "sha512-KO0WTob4JEApv69iYp1eGvfMSUkgw//IpMnq+//cORBzXf0smyRwPLrUvEe5qtAEGjwZTXrjxg+oJNP/C00t6w==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-query": {
|
||||
"version": "5.85.5",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.85.5.tgz",
|
||||
"integrity": "sha512-/X4EFNcnPiSs8wM2v+b6DqS5mmGeuJQvxBglmDxl6ZQb5V26ouD2SJYAcC3VjbNwqhY2zjxVD15rDA5nGbMn3A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/query-core": "5.85.5"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz",
|
||||
@ -1531,6 +1655,16 @@
|
||||
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
||||
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
@ -1590,6 +1724,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ansi-colors": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
|
||||
"integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
@ -1827,6 +1971,12 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/available-typed-arrays": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
||||
@ -1853,6 +2003,17 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
|
||||
"integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axobject-query": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||
@ -1930,7 +2091,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
@ -1984,6 +2144,13 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/change-case": {
|
||||
"version": "5.4.4",
|
||||
"resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz",
|
||||
"integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
@ -2058,6 +2225,25 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/colorette": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
|
||||
"integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@ -2065,6 +2251,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
|
||||
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/core-js": {
|
||||
"version": "3.45.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.0.tgz",
|
||||
@ -2231,6 +2426,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/doctrine": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
|
||||
@ -2248,7 +2452,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
@ -2349,7 +2552,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@ -2359,7 +2561,6 @@
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@ -2397,7 +2598,6 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
@ -2410,7 +2610,6 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
@ -3474,6 +3673,26 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/for-each": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
||||
@ -3490,6 +3709,22 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
@ -3509,7 +3744,6 @@
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@ -3550,7 +3784,6 @@
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
@ -3575,7 +3808,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
@ -3663,7 +3895,6 @@
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@ -3742,7 +3973,6 @@
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@ -3755,7 +3985,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
@ -3771,7 +4000,6 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
@ -3797,6 +4025,20 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/https-proxy-agent": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
|
||||
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"agent-base": "^7.1.2",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/hyperdyperid": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz",
|
||||
@ -3844,6 +4086,19 @@
|
||||
"node": ">=0.8.19"
|
||||
}
|
||||
},
|
||||
"node_modules/index-to-position": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.1.0.tgz",
|
||||
"integrity": "sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/internal-slot": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
|
||||
@ -4391,6 +4646,16 @@
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/js-levenshtein": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
|
||||
"integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@ -4545,7 +4810,6 @@
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@ -4615,6 +4879,27 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
@ -4801,6 +5086,40 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/openapi-typescript": {
|
||||
"version": "7.9.1",
|
||||
"resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-7.9.1.tgz",
|
||||
"integrity": "sha512-9gJtoY04mk6iPMbToPjPxEAtfXZ0dTsMZtsgUI8YZta0btPPig9DJFP4jlerQD/7QOwYgb0tl+zLUpDf7vb7VA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@redocly/openapi-core": "^1.34.5",
|
||||
"ansi-colors": "^4.1.3",
|
||||
"change-case": "^5.4.4",
|
||||
"parse-json": "^8.3.0",
|
||||
"supports-color": "^10.1.0",
|
||||
"yargs-parser": "^21.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"openapi-typescript": "bin/cli.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.x"
|
||||
}
|
||||
},
|
||||
"node_modules/openapi-typescript/node_modules/supports-color": {
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.0.tgz",
|
||||
"integrity": "sha512-5eG9FQjEjDbAlI5+kdpdyPIBMRH4GfTVDGREVupaZHmVoppknhM29b/S9BkQz7cathp85BVgRi/As3Siln7e0Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||
@ -4882,6 +5201,24 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-json": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz",
|
||||
"integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.26.2",
|
||||
"index-to-position": "^1.1.0",
|
||||
"type-fest": "^4.39.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
@ -4929,6 +5266,16 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pluralize": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
|
||||
"integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/possible-typed-array-names": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
||||
@ -4961,6 +5308,12 @@
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
@ -5030,6 +5383,28 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.8.1.tgz",
|
||||
"integrity": "sha512-5cy/M8DHcG51/KUIka1nfZ2QeylS4PJRs6TT8I4PF5axVsI5JUxp0hC0NZ/AEEj8Vw7xsEoD7L/6FY+zoYaOGA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie": "^1.0.1",
|
||||
"set-cookie-parser": "^2.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
@ -5331,6 +5706,12 @@
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
||||
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
@ -5836,6 +6217,19 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
|
||||
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
|
||||
"dev": true,
|
||||
"license": "(MIT OR CC0-1.0)",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/typed-array-buffer": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
|
||||
@ -5999,6 +6393,13 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uri-js-replace": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz",
|
||||
"integrity": "sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
@ -6114,6 +6515,23 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yaml-ast-parser": {
|
||||
"version": "0.0.43",
|
||||
"resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz",
|
||||
"integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/yargs-parser": {
|
||||
"version": "21.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
|
||||
@ -8,22 +8,28 @@
|
||||
"dev": "rsbuild dev",
|
||||
"preview": "rsbuild preview",
|
||||
"lint": "eslint",
|
||||
"typecheck": "tsc --noEmit"
|
||||
"typecheck": "tsc --noEmit",
|
||||
"openapi:gen": "npx openapi-typescript http://localhost:3000/docs/swagger.json -o src/shared/api/schema.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "5.85.5",
|
||||
"axios": "1.11.0",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1"
|
||||
"react-dom": "19.1.1",
|
||||
"react-router": "7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rsbuild/core": "1.4.15",
|
||||
"@rsbuild/plugin-eslint": "1.1.2",
|
||||
"@rsbuild/plugin-react": "1.3.5",
|
||||
"@rsbuild/plugin-type-check": "1.2.4",
|
||||
"@tanstack/eslint-plugin-query": "5.83.1",
|
||||
"@types/react": "19.1.9",
|
||||
"@types/react-dom": "19.1.7",
|
||||
"@typescript-eslint/parser": "8.39.0",
|
||||
"eslint": "9.33.0",
|
||||
"eslint-config-ksv741": "0.2.0",
|
||||
"openapi-typescript": "7.9.1",
|
||||
"typescript": "5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { defineConfig } from '@rsbuild/core';
|
||||
import { pluginReact } from '@rsbuild/plugin-react';
|
||||
import { pluginEslint } from '@rsbuild/plugin-eslint';
|
||||
import { pluginReact } from '@rsbuild/plugin-react';
|
||||
import { pluginTypeCheck } from '@rsbuild/plugin-type-check';
|
||||
|
||||
export default defineConfig({
|
||||
@ -9,6 +9,8 @@ export default defineConfig({
|
||||
pluginEslint({
|
||||
eslintPluginOptions: {
|
||||
configType: 'flat',
|
||||
failOnError: false,
|
||||
failOnWarning: false,
|
||||
},
|
||||
}),
|
||||
pluginTypeCheck(),
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
const App = () => (
|
||||
<div>
|
||||
<h1>Investments Control Frontend</h1>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default App;
|
||||
1
frontend/src/app/App/index.ts
Normal file
1
frontend/src/app/App/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as App } from './ui/App';
|
||||
36
frontend/src/app/App/ui/App.tsx
Normal file
36
frontend/src/app/App/ui/App.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import './normalize.css';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import {
|
||||
BrowserRouter,
|
||||
Route,
|
||||
Routes,
|
||||
} from 'react-router';
|
||||
|
||||
import { LoginPage, RegisterPage } from '@/pages/Authorization';
|
||||
import { HomePage } from '@/pages/Home';
|
||||
import { AuthContextProvider } from '@/shared/context/AuthContext';
|
||||
import { AuthRoute } from '@/shared/ui/AuthRoute';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const App = () => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AuthContextProvider>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route element={<AuthRoute />}>
|
||||
<Route element={<HomePage />} path="/" />
|
||||
</Route>
|
||||
|
||||
<Route element={<AuthRoute needAuth={false} to="/" />}>
|
||||
<Route element={<LoginPage />} path="/login" />
|
||||
|
||||
<Route element={<RegisterPage />} path="/register" />
|
||||
</Route>
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</AuthContextProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
export default App;
|
||||
349
frontend/src/app/App/ui/normalize.css
vendored
Normal file
349
frontend/src/app/App/ui/normalize.css
vendored
Normal file
@ -0,0 +1,349 @@
|
||||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
||||
|
||||
/* Document
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Correct the line height in all browsers.
|
||||
* 2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.15; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/* Sections
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the margin in all browsers.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the `main` element consistently in IE.
|
||||
*/
|
||||
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the font size and margin on `h1` elements within `section` and
|
||||
* `article` contexts in Chrome, Firefox, and Safari.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in Firefox.
|
||||
* 2. Show the overflow in Edge and IE.
|
||||
*/
|
||||
|
||||
hr {
|
||||
box-sizing: content-box; /* 1 */
|
||||
height: 0; /* 1 */
|
||||
overflow: visible; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the gray background on active links in IE 10.
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove the bottom border in Chrome 57-
|
||||
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none; /* 1 */
|
||||
text-decoration: underline; /* 2 */
|
||||
text-decoration: underline dotted; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||
* all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the border on images inside links in IE 10.
|
||||
*/
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Change the font styles in all browsers.
|
||||
* 2. Remove the margin in Firefox and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit; /* 1 */
|
||||
font-size: 100%; /* 1 */
|
||||
line-height: 1.15; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the overflow in IE.
|
||||
* 1. Show the overflow in Edge.
|
||||
*/
|
||||
|
||||
button,
|
||||
input { /* 1 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||
* 1. Remove the inheritance of text transform in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select { /* 1 */
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the inability to style clickable types in iOS and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
[type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner border and padding in Firefox.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the focus styles unset by the previous rule.
|
||||
*/
|
||||
|
||||
button:-moz-focusring,
|
||||
[type="button"]:-moz-focusring,
|
||||
[type="reset"]:-moz-focusring,
|
||||
[type="submit"]:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the padding in Firefox.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
padding: 0.35em 0.75em 0.625em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the text wrapping in Edge and IE.
|
||||
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||
* 3. Remove the padding so developers are not caught out when they zero out
|
||||
* `fieldset` elements in all browsers.
|
||||
*/
|
||||
|
||||
legend {
|
||||
box-sizing: border-box; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
display: table; /* 1 */
|
||||
max-width: 100%; /* 1 */
|
||||
padding: 0; /* 3 */
|
||||
white-space: normal; /* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default vertical scrollbar in IE 10+.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in IE 10.
|
||||
* 2. Remove the padding in IE 10.
|
||||
*/
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||
*/
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the odd appearance in Chrome and Safari.
|
||||
* 2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
outline-offset: -2px; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||
* 2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/* Interactive
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Add the correct display in Edge, IE 10+, and Firefox.
|
||||
*/
|
||||
|
||||
details {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the correct display in all browsers.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* Misc
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10+.
|
||||
*/
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
3
frontend/src/entity/account/index.ts
Normal file
3
frontend/src/entity/account/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export { useLogin } from './login/useLogin';
|
||||
export { useRegister } from './register/useRegister';
|
||||
export { useLogout } from './logout/useLogout';
|
||||
37
frontend/src/entity/account/login/useLogin.ts
Normal file
37
frontend/src/entity/account/login/useLogin.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { useContext } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
import { api } from '@/shared/api/client';
|
||||
import { AuthContext } from '@/shared/context/AuthContext';
|
||||
|
||||
import type { paths } from '@/shared/api/schema';
|
||||
|
||||
type LoginRequestBody = paths['/auth/login']['post']['requestBody']['content']['application/json'];
|
||||
type LoginSuccessResponse = paths['/auth/login']['post']['responses']['200']['content']['application/json'];
|
||||
|
||||
async function login(data: LoginRequestBody): Promise<LoginSuccessResponse> {
|
||||
const { data: result } = await api.post<LoginSuccessResponse>('/auth/login', data);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export const useLogin = () => {
|
||||
const navigate = useNavigate();
|
||||
const { setAccessToken } = useContext(AuthContext);
|
||||
|
||||
return useMutation({
|
||||
mutationFn: login,
|
||||
onSuccess: async (data) => {
|
||||
setAccessToken(data.accessToken);
|
||||
localStorage.setItem('accessToken', data.accessToken);
|
||||
await navigate('/another');
|
||||
},
|
||||
onError: (error) => {
|
||||
// eslint-disable-next-line
|
||||
console.log('Неуспешный логин', error);
|
||||
|
||||
throw error;
|
||||
},
|
||||
});
|
||||
};
|
||||
30
frontend/src/entity/account/logout/useLogout.ts
Normal file
30
frontend/src/entity/account/logout/useLogout.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { useContext } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
import { api } from '@/shared/api/client';
|
||||
import { AuthContext } from '@/shared/context/AuthContext';
|
||||
|
||||
import type { paths } from '@/shared/api/schema';
|
||||
|
||||
type LogoutSuccessResponse = paths['/auth/logout']['post']['responses']['200']['content']['application/json'];
|
||||
|
||||
async function logout() {
|
||||
const { data: result } = await api.post<LogoutSuccessResponse>('/auth/logout');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export const useLogout = () => {
|
||||
const navigate = useNavigate();
|
||||
const { setAccessToken } = useContext(AuthContext);
|
||||
|
||||
return useMutation({
|
||||
mutationFn: logout,
|
||||
onSuccess: async () => {
|
||||
setAccessToken('');
|
||||
localStorage.removeItem('accessToken');
|
||||
await navigate('/login');
|
||||
},
|
||||
});
|
||||
};
|
||||
22
frontend/src/entity/account/register/useRegister.ts
Normal file
22
frontend/src/entity/account/register/useRegister.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '@/shared/api/client';
|
||||
|
||||
import type { paths } from '@/shared/api/schema';
|
||||
|
||||
type RegisterRequestBody = paths['/auth/register']['post']['requestBody']['content']['application/json'];
|
||||
type RegisterSuccessResponse = paths['/auth/register']['post']['responses']['200']['content']['application/json'];
|
||||
|
||||
async function register(data: RegisterRequestBody) {
|
||||
const { data: result } = await api.post<RegisterSuccessResponse>('auth/register', data);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export const useRegister = () => useMutation({
|
||||
mutationFn: register,
|
||||
onError: (error) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Неуспешная регистрация', error);
|
||||
},
|
||||
});
|
||||
1
frontend/src/features/auth/LogoutButton/index.ts
Normal file
1
frontend/src/features/auth/LogoutButton/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as LogoutButton } from './ui/LogoutButton.ui';
|
||||
@ -0,0 +1,21 @@
|
||||
import { useLogout } from '@/entity/account';
|
||||
|
||||
const LogoutButton = () => {
|
||||
const { mutate: logout, isPending } = useLogout();
|
||||
|
||||
const onClickHandler = async () => {
|
||||
await logout();
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
disabled={isPending}
|
||||
type="button"
|
||||
onClick={onClickHandler}
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default LogoutButton;
|
||||
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
|
||||
import { App } from '@/app/App';
|
||||
|
||||
const rootEl = document.getElementById('root');
|
||||
if (rootEl) {
|
||||
|
||||
2
frontend/src/pages/Authorization/index.ts
Normal file
2
frontend/src/pages/Authorization/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { default as LoginPage } from './ui/Login/LoginPage.ui';
|
||||
export { default as RegisterPage } from './ui/Register/RegisterPage.ui';
|
||||
@ -0,0 +1,19 @@
|
||||
.overlay {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: darkgrey;
|
||||
}
|
||||
|
||||
.contentWrapper {
|
||||
width: 400px;
|
||||
height: 500px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
border-radius: 10px;
|
||||
background: lightgray;
|
||||
}
|
||||
20
frontend/src/pages/Authorization/ui/Login/LoginPage.ui.tsx
Normal file
20
frontend/src/pages/Authorization/ui/Login/LoginPage.ui.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { NavLink } from 'react-router';
|
||||
|
||||
import { LoginForm } from '@/widgets/auth/LoginForm';
|
||||
|
||||
import classes from './LoginPage.module.css';
|
||||
|
||||
const LoginPage = () => (
|
||||
<div className={classes.overlay}>
|
||||
<div className={classes.contentWrapper}>
|
||||
<h1>Вход в аккаунт</h1>
|
||||
|
||||
<LoginForm />
|
||||
|
||||
<NavLink end to="/register">
|
||||
Регистрация
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
export default LoginPage;
|
||||
@ -0,0 +1,19 @@
|
||||
.overlay {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: darkgrey;
|
||||
}
|
||||
|
||||
.contentWrapper {
|
||||
width: 400px;
|
||||
height: 500px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
border-radius: 10px;
|
||||
background: lightgray;
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
import { NavLink } from 'react-router';
|
||||
|
||||
import { RegisterForm } from '@/widgets/auth/RegisterForm';
|
||||
|
||||
import classes from './RegisterPage.module.css';
|
||||
|
||||
const RegisterPage = () => (
|
||||
<div className={classes.overlay}>
|
||||
<div className={classes.contentWrapper}>
|
||||
<h1>Регистрация аккаунта</h1>
|
||||
|
||||
<RegisterForm />
|
||||
|
||||
<NavLink end to="/login">
|
||||
Вход
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default RegisterPage;
|
||||
1
frontend/src/pages/Home/index.ts
Normal file
1
frontend/src/pages/Home/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as HomePage } from './ui/HomePage.ui';
|
||||
51
frontend/src/pages/Home/ui/HomePage.ui.tsx
Normal file
51
frontend/src/pages/Home/ui/HomePage.ui.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import { useContext, useState } from 'react';
|
||||
|
||||
import { api } from '@/shared/api/client';
|
||||
import { AuthContext } from '@/shared/context/AuthContext';
|
||||
|
||||
import type { paths } from '@/shared/api/schema';
|
||||
|
||||
type Response = paths['/auth/me']['get']['responses']['200']['content']['application/json'];
|
||||
|
||||
const fetchProfile = async () => {
|
||||
const { data } = await api.get<Response>('/auth/me');
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const HomePage = () => {
|
||||
const [info, setInfo] = useState<Response>();
|
||||
const { accessToken } = useContext(AuthContext);
|
||||
|
||||
const onClick = () => {
|
||||
if (!accessToken) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetchProfile()
|
||||
.then((data) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('DATA', data);
|
||||
|
||||
setInfo(data);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
Home page only for auth user
|
||||
<button type="button" onClick={onClick}>
|
||||
get profile
|
||||
</button>
|
||||
|
||||
<br />
|
||||
|
||||
<b>
|
||||
ID:
|
||||
{info?.id}
|
||||
</b>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HomePage;
|
||||
46
frontend/src/shared/api/client.ts
Normal file
46
frontend/src/shared/api/client.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import axios from 'axios';
|
||||
|
||||
import type { paths } from './schema';
|
||||
|
||||
const BASE_URL = 'http://localhost:3000';
|
||||
|
||||
export const api = axios.create({
|
||||
baseURL: BASE_URL,
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
api.interceptors.request.use(
|
||||
(request) => {
|
||||
const accessToken = localStorage.getItem('accessToken');
|
||||
if (accessToken) {
|
||||
request.headers.set('Authorization', `Bearer ${accessToken}`);
|
||||
}
|
||||
|
||||
return request;
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
|
||||
async (error) => Promise.reject(error),
|
||||
);
|
||||
|
||||
type RefreshTokensSuccessResponse = paths['/auth/refresh']['post']['responses']['200']['content']['application/json'];
|
||||
|
||||
api.interceptors.response.use((response) => response, async (error) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
|
||||
const originalRequest = error.config;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
if (error.response?.status === 401 && !originalRequest._retry) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
originalRequest._retry = true;
|
||||
const { data: result } = await axios.post<RefreshTokensSuccessResponse>(`${BASE_URL}/auth/refresh`, null, {
|
||||
withCredentials: true,
|
||||
});
|
||||
const { accessToken } = result;
|
||||
localStorage.setItem('accessToken', accessToken);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access
|
||||
return api(error.config);
|
||||
}
|
||||
|
||||
throw error;
|
||||
});
|
||||
335
frontend/src/shared/api/schema.ts
Normal file
335
frontend/src/shared/api/schema.ts
Normal file
@ -0,0 +1,335 @@
|
||||
/**
|
||||
* This file was auto-generated by openapi-typescript.
|
||||
* Do not make direct changes to the file.
|
||||
*/
|
||||
|
||||
export interface paths {
|
||||
"/auth/register": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/**
|
||||
* Регистрация
|
||||
* @description Регистрация аккаунта
|
||||
*/
|
||||
post: operations["AuthController_register"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/auth/login": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/**
|
||||
* Логин
|
||||
* @description Вход в аккаунт
|
||||
*/
|
||||
post: operations["AuthController_login"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/auth/logout": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/**
|
||||
* Логаут
|
||||
* @description Выход из аккаунта
|
||||
*/
|
||||
post: operations["AuthController_logout"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/auth/me": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/**
|
||||
* Получить профиль
|
||||
* @description Получить данные о своем профиле
|
||||
*/
|
||||
get: operations["AuthController_me"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/auth/refresh": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Обновление access и refresh токенов */
|
||||
post: operations["AuthController_refresh"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
}
|
||||
export type webhooks = Record<string, never>;
|
||||
export interface components {
|
||||
schemas: {
|
||||
RegisterDto: {
|
||||
/**
|
||||
* @description Email
|
||||
* @example mail@example.com
|
||||
*/
|
||||
email: string;
|
||||
/**
|
||||
* @description Пароль
|
||||
* @example 123456
|
||||
*/
|
||||
password: string;
|
||||
};
|
||||
RegisterValidationDto: {
|
||||
/** @example 400 */
|
||||
statusCode: number;
|
||||
/** @example [
|
||||
* "Email должен быть строкой.",
|
||||
* "Некорректный формат email.",
|
||||
* "Email обязателен для заполнения.",
|
||||
* "Пароль должен быть строкой.",
|
||||
* "Пароль обязателен для заполнения.",
|
||||
* "Пароль должен содержать минимум 6 символов."
|
||||
* ] */
|
||||
message: string[];
|
||||
/** @example Bad Request */
|
||||
error: string;
|
||||
};
|
||||
ConflictRegisterDto: {
|
||||
/** @example 400 */
|
||||
statusCode: number;
|
||||
/** @example Пользователь с такой почтой уже зарегистрирован */
|
||||
message: Record<string, never>;
|
||||
/** @example Conflict */
|
||||
error: string;
|
||||
};
|
||||
LoginDto: {
|
||||
/**
|
||||
* @description Email
|
||||
* @example mail@example.com
|
||||
*/
|
||||
email: string;
|
||||
/**
|
||||
* @description Пароль
|
||||
* @example 123456
|
||||
*/
|
||||
password: string;
|
||||
};
|
||||
SuccessLoginDto: {
|
||||
/** @description Access токен */
|
||||
accessToken: string;
|
||||
};
|
||||
IncorrectLoginDto: {
|
||||
/** @example 400 */
|
||||
statusCode: number;
|
||||
/** @example Неверный логин или пароль */
|
||||
message: Record<string, never>;
|
||||
/** @example Bad Request */
|
||||
error: string;
|
||||
};
|
||||
CurrentProfileDto: {
|
||||
/** @description Индетификатор аккаунта */
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
responses: never;
|
||||
parameters: never;
|
||||
requestBodies: never;
|
||||
headers: never;
|
||||
pathItems: never;
|
||||
}
|
||||
export type $defs = Record<string, never>;
|
||||
export interface operations {
|
||||
AuthController_register: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["RegisterDto"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Успешная регистрация */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["RegisterDto"];
|
||||
};
|
||||
};
|
||||
/** @description Ошибка валидации */
|
||||
400: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["RegisterValidationDto"];
|
||||
};
|
||||
};
|
||||
/** @description Ошибка создания аккаунта */
|
||||
409: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["ConflictRegisterDto"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
AuthController_login: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["LoginDto"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Успешный вход в аккаунт */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["SuccessLoginDto"];
|
||||
};
|
||||
};
|
||||
/** @description Неверный логин или пароль */
|
||||
400: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["IncorrectLoginDto"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
AuthController_logout: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Успешный выход из аккаунт */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
AuthController_me: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Успешное обновление токенов */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["CurrentProfileDto"];
|
||||
};
|
||||
};
|
||||
/** @description Пользователь не авторизован */
|
||||
401: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
AuthController_refresh: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Успешное обновление токенов */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["SuccessLoginDto"];
|
||||
};
|
||||
};
|
||||
/** @description Пользователь не авторизован */
|
||||
401: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
404: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
43
frontend/src/shared/context/AuthContext/AuthContext.ui.tsx
Normal file
43
frontend/src/shared/context/AuthContext/AuthContext.ui.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import {
|
||||
createContext,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import type {
|
||||
Dispatch,
|
||||
FC,
|
||||
ReactNode,
|
||||
SetStateAction,
|
||||
} from 'react';
|
||||
|
||||
export type AuthContextType = {
|
||||
accessToken: string;
|
||||
setAccessToken: Dispatch<SetStateAction<string>>;
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
export const AuthContext = createContext<AuthContextType>(null as unknown as AuthContextType);
|
||||
|
||||
function getInitialToken() {
|
||||
return localStorage.getItem('accessToken') ?? '';
|
||||
}
|
||||
|
||||
type AuthContextProviderProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
export const AuthContextProvider: FC<AuthContextProviderProps> = (props) => {
|
||||
const { children } = props;
|
||||
|
||||
const [accessToken, setAccessToken] = useState<string>(getInitialToken);
|
||||
|
||||
const value = useMemo(() => ({
|
||||
accessToken,
|
||||
setAccessToken,
|
||||
}), [accessToken]);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={value}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
1
frontend/src/shared/context/AuthContext/index.ts
Normal file
1
frontend/src/shared/context/AuthContext/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { AuthContext, AuthContextProvider } from './AuthContext.ui';
|
||||
1
frontend/src/shared/router/index.ts
Normal file
1
frontend/src/shared/router/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { PAGES } from './types';
|
||||
5
frontend/src/shared/router/types.ts
Normal file
5
frontend/src/shared/router/types.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export enum PAGES {
|
||||
Home = '/',
|
||||
Login = '/login',
|
||||
Register = '/register',
|
||||
}
|
||||
1
frontend/src/shared/ui/AuthRoute/index.ts
Normal file
1
frontend/src/shared/ui/AuthRoute/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as AuthRoute } from './ui/AuthRoute.ui';
|
||||
28
frontend/src/shared/ui/AuthRoute/ui/AuthRoute.ui.tsx
Normal file
28
frontend/src/shared/ui/AuthRoute/ui/AuthRoute.ui.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import { type FC, useContext } from 'react';
|
||||
import { Navigate, Outlet } from 'react-router';
|
||||
|
||||
import { AuthContext } from '@/shared/context/AuthContext';
|
||||
|
||||
import type { NavigateProps } from 'react-router';
|
||||
|
||||
type AuthRouteProps = {
|
||||
needAuth?: boolean;
|
||||
to?: NavigateProps['to'];
|
||||
};
|
||||
const AuthRoute: FC<AuthRouteProps> = (props) => {
|
||||
const {
|
||||
needAuth = true,
|
||||
to = '/login',
|
||||
} = props;
|
||||
const { accessToken } = useContext(AuthContext);
|
||||
|
||||
if ((needAuth && accessToken) || (!needAuth && !accessToken)) {
|
||||
return <Outlet />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Navigate to={to} />
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthRoute;
|
||||
1
frontend/src/shared/ui/input/index.ts
Normal file
1
frontend/src/shared/ui/input/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as Input } from './ui/Input.ui';
|
||||
4
frontend/src/shared/ui/input/ui/Input.styles.module.css
Normal file
4
frontend/src/shared/ui/input/ui/Input.styles.module.css
Normal file
@ -0,0 +1,4 @@
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
24
frontend/src/shared/ui/input/ui/Input.ui.tsx
Normal file
24
frontend/src/shared/ui/input/ui/Input.ui.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import classes from './Input.styles.module.css';
|
||||
|
||||
import type { FC, HTMLProps } from 'react';
|
||||
|
||||
type InputProps = HTMLProps<HTMLInputElement> & {
|
||||
label: string;
|
||||
};
|
||||
|
||||
const Input: FC<InputProps> = (props) => {
|
||||
const { label, id, ...rest } = props;
|
||||
|
||||
return (
|
||||
<label className={classes.wrapper} htmlFor={id}>
|
||||
{label}
|
||||
|
||||
<input
|
||||
id={id}
|
||||
{...rest}
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
export default Input;
|
||||
3
frontend/src/shared/utils/index.ts
Normal file
3
frontend/src/shared/utils/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const wait = async (time: number) => new Promise((res) => {
|
||||
setTimeout(res, time);
|
||||
});
|
||||
1
frontend/src/widgets/auth/LoginForm/index.ts
Normal file
1
frontend/src/widgets/auth/LoginForm/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as LoginForm } from './ui/LoginForm.ui';
|
||||
10
frontend/src/widgets/auth/LoginForm/ui/LoginForm.module.css
Normal file
10
frontend/src/widgets/auth/LoginForm/ui/LoginForm.module.css
Normal file
@ -0,0 +1,10 @@
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
width: 300px;
|
||||
|
||||
button:disabled {
|
||||
background: red;
|
||||
}
|
||||
}
|
||||
39
frontend/src/widgets/auth/LoginForm/ui/LoginForm.ui.tsx
Normal file
39
frontend/src/widgets/auth/LoginForm/ui/LoginForm.ui.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import { Input } from '@/shared/ui/input';
|
||||
|
||||
import { useLoginForm } from './hooks/useLoginForm';
|
||||
import classes from './LoginForm.module.css';
|
||||
|
||||
import type { FC } from 'react';
|
||||
|
||||
const LoginForm: FC = () => {
|
||||
const { isPending, onSubmitHandler } = useLoginForm();
|
||||
|
||||
return (
|
||||
<form className={classes.form} onSubmit={onSubmitHandler}>
|
||||
<Input
|
||||
disabled={isPending}
|
||||
id="email"
|
||||
label="Почта"
|
||||
name="email"
|
||||
type="text"
|
||||
/>
|
||||
|
||||
<Input
|
||||
disabled={isPending}
|
||||
id="password"
|
||||
label="Пароль"
|
||||
name="password"
|
||||
type="password"
|
||||
/>
|
||||
|
||||
<button
|
||||
disabled={isPending}
|
||||
type="submit"
|
||||
>
|
||||
Войти
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginForm;
|
||||
23
frontend/src/widgets/auth/LoginForm/ui/hooks/useLoginForm.ts
Normal file
23
frontend/src/widgets/auth/LoginForm/ui/hooks/useLoginForm.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { useLogin } from '@/entity/account';
|
||||
|
||||
import type { FormEventHandler } from 'react';
|
||||
|
||||
export const useLoginForm = () => {
|
||||
const { mutate: login, isPending } = useLogin();
|
||||
|
||||
const onSubmitHandler: FormEventHandler<HTMLFormElement> = (event) => {
|
||||
event.preventDefault();
|
||||
const formData = new FormData(event.currentTarget);
|
||||
const email = formData.get('email');
|
||||
const password = formData.get('password');
|
||||
|
||||
if (typeof email === 'string' && typeof password === 'string') {
|
||||
login({ email, password });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
isPending,
|
||||
onSubmitHandler,
|
||||
};
|
||||
};
|
||||
1
frontend/src/widgets/auth/RegisterForm/index.ts
Normal file
1
frontend/src/widgets/auth/RegisterForm/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as RegisterForm } from './ui/RegisterForm.ui';
|
||||
@ -0,0 +1,10 @@
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
width: 300px;
|
||||
|
||||
button:disabled {
|
||||
background: red;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
import { Input } from '@/shared/ui/input';
|
||||
|
||||
import { useRegisterForm } from './hooks/useRegisterForm';
|
||||
import classes from './RegisterForm.module.css';
|
||||
|
||||
import type { FC } from 'react';
|
||||
|
||||
const RegisterForm: FC = () => {
|
||||
const { onSubmitHandler, isPending } = useRegisterForm();
|
||||
|
||||
return (
|
||||
<form className={classes.form} onSubmit={onSubmitHandler}>
|
||||
<Input
|
||||
disabled={isPending}
|
||||
id="email"
|
||||
label="Почта"
|
||||
name="email"
|
||||
type="text"
|
||||
/>
|
||||
|
||||
<Input
|
||||
disabled={isPending}
|
||||
id="password"
|
||||
label="Пароль"
|
||||
name="password"
|
||||
type="password"
|
||||
/>
|
||||
|
||||
<button disabled={isPending} type="submit">
|
||||
Зарегистрироваться
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegisterForm;
|
||||
@ -0,0 +1,23 @@
|
||||
import { useRegister } from '@/entity/account';
|
||||
|
||||
import type { FormEventHandler } from 'react';
|
||||
|
||||
export const useRegisterForm = () => {
|
||||
const { mutate: register, isPending } = useRegister();
|
||||
|
||||
const onSubmitHandler: FormEventHandler<HTMLFormElement> = (event) => {
|
||||
event.preventDefault();
|
||||
const formData = new FormData(event.currentTarget);
|
||||
const email = formData.get('email');
|
||||
const password = formData.get('password');
|
||||
|
||||
if (typeof email === 'string' && typeof password === 'string') {
|
||||
register({ email, password });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
onSubmitHandler,
|
||||
isPending,
|
||||
};
|
||||
};
|
||||
@ -19,7 +19,10 @@
|
||||
/* type checking */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true
|
||||
"noUnusedParameters": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user