Теперь есть возможность регистрации и авторизации. Осталось настроить выход и автопродление токена))
This commit is contained in:
225
online-library/package-lock.json
generated
225
online-library/package-lock.json
generated
@@ -14,7 +14,9 @@
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"axios": "^1.12.2",
|
||||
"bootstrap": "^5.3.8",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"react": "^19.1.1",
|
||||
"react-bootstrap": "^2.10.10",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-router-dom": "^7.8.2",
|
||||
"react-router-hash-link": "^2.4.3",
|
||||
@@ -2882,12 +2884,75 @@
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/ssr": {
|
||||
"version": "3.9.10",
|
||||
"resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz",
|
||||
"integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==",
|
||||
"dependencies": {
|
||||
"@swc/helpers": "^0.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@restart/hooks": {
|
||||
"version": "0.4.16",
|
||||
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz",
|
||||
"integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==",
|
||||
"dependencies": {
|
||||
"dequal": "^2.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@restart/ui": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.9.4.tgz",
|
||||
"integrity": "sha512-N4C7haUc3vn4LTwVUPlkJN8Ach/+yIMvRuTVIhjilNHqegY60SGLrzud6errOMNJwSnmYFnt1J0H/k8FE3A4KA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.26.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@react-aria/ssr": "^3.5.0",
|
||||
"@restart/hooks": "^0.5.0",
|
||||
"@types/warning": "^3.0.3",
|
||||
"dequal": "^2.0.3",
|
||||
"dom-helpers": "^5.2.0",
|
||||
"uncontrollable": "^8.0.4",
|
||||
"warning": "^4.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.14.0",
|
||||
"react-dom": ">=16.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@restart/ui/node_modules/@restart/hooks": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.5.1.tgz",
|
||||
"integrity": "sha512-EMoH04NHS1pbn07iLTjIjgttuqb7qu4+/EyhAx27MHpoENcB2ZdSsLTNxmKD+WEPnZigo62Qc8zjGnNxoSE/5Q==",
|
||||
"dependencies": {
|
||||
"dequal": "^2.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@restart/ui/node_modules/uncontrollable": {
|
||||
"version": "8.0.4",
|
||||
"resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz",
|
||||
"integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-babel": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
|
||||
@@ -3211,6 +3276,14 @@
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.5.17",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
|
||||
"integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/dom": {
|
||||
"version": "10.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
|
||||
@@ -3537,6 +3610,11 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz",
|
||||
"integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA=="
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="
|
||||
},
|
||||
"node_modules/@types/q": {
|
||||
"version": "1.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.8.tgz",
|
||||
@@ -3552,6 +3630,22 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
|
||||
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.1.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz",
|
||||
"integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==",
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-transition-group": {
|
||||
"version": "4.4.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
|
||||
"integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/resolve": {
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
|
||||
@@ -3615,6 +3709,11 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="
|
||||
},
|
||||
"node_modules/@types/warning": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz",
|
||||
"integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q=="
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
||||
@@ -5247,6 +5346,11 @@
|
||||
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz",
|
||||
"integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="
|
||||
},
|
||||
"node_modules/classnames": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
|
||||
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
|
||||
},
|
||||
"node_modules/clean-css": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz",
|
||||
@@ -5944,6 +6048,11 @@
|
||||
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
|
||||
"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
||||
},
|
||||
"node_modules/damerau-levenshtein": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
||||
@@ -6239,6 +6348,15 @@
|
||||
"utila": "~0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-helpers": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
||||
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.8.7",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-serializer": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
|
||||
@@ -8607,6 +8725,14 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/invariant": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz",
|
||||
@@ -10246,6 +10372,14 @@
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jwt-decode": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
|
||||
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
@@ -12625,6 +12759,23 @@
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types-extra": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz",
|
||||
"integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==",
|
||||
"dependencies": {
|
||||
"react-is": "^16.3.2",
|
||||
"warning": "^4.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=0.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types-extra/node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"node_modules/prop-types/node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
@@ -12795,6 +12946,36 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/react-bootstrap": {
|
||||
"version": "2.10.10",
|
||||
"resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.10.tgz",
|
||||
"integrity": "sha512-gMckKUqn8aK/vCnfwoBpBVFUGT9SVQxwsYrp9yDHt0arXMamxALerliKBxr1TPbntirK/HGrUAHYbAeQTa9GHQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.24.7",
|
||||
"@restart/hooks": "^0.4.9",
|
||||
"@restart/ui": "^1.9.4",
|
||||
"@types/prop-types": "^15.7.12",
|
||||
"@types/react-transition-group": "^4.4.6",
|
||||
"classnames": "^2.3.2",
|
||||
"dom-helpers": "^5.2.1",
|
||||
"invariant": "^2.2.4",
|
||||
"prop-types": "^15.8.1",
|
||||
"prop-types-extra": "^1.1.0",
|
||||
"react-transition-group": "^4.4.5",
|
||||
"uncontrollable": "^7.2.1",
|
||||
"warning": "^4.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": ">=16.14.8",
|
||||
"react": ">=16.14.0",
|
||||
"react-dom": ">=16.14.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-dev-utils": {
|
||||
"version": "12.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",
|
||||
@@ -12915,6 +13096,11 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
},
|
||||
"node_modules/react-lifecycles-compat": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
|
||||
@@ -13051,6 +13237,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-transition-group": {
|
||||
"version": "4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"dom-helpers": "^5.0.1",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.6.0",
|
||||
"react-dom": ">=16.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
@@ -15251,6 +15452,20 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/uncontrollable": {
|
||||
"version": "7.2.1",
|
||||
"resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz",
|
||||
"integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.6.3",
|
||||
"@types/react": ">=16.9.11",
|
||||
"invariant": "^2.2.4",
|
||||
"react-lifecycles-compat": "^3.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/underscore": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz",
|
||||
@@ -15478,6 +15693,14 @@
|
||||
"makeerror": "1.0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/warning": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/watchpack": {
|
||||
"version": "2.4.4",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz",
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"axios": "^1.12.2",
|
||||
"bootstrap": "^5.3.8",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"react": "^19.1.1",
|
||||
"react-bootstrap": "^2.10.10",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-router-dom": "^7.8.2",
|
||||
"react-router-hash-link": "^2.4.3",
|
||||
|
||||
@@ -1,8 +1,34 @@
|
||||
import axios from "axios";
|
||||
import {
|
||||
getAccessToken,
|
||||
isTokenExpired,
|
||||
removeAccessToken,
|
||||
setAccessToken,
|
||||
} from "./JwtUtils";
|
||||
|
||||
const ONLINE_LIBRARY_URL = "http://localhost:8080/api/v1.0";
|
||||
let isRefreshing = false;
|
||||
let failedQueue = [];
|
||||
const processQueue = (error, token = null) => {
|
||||
failedQueue.forEach((prom) => {
|
||||
if (error) {
|
||||
prom.reject(error);
|
||||
} else {
|
||||
prom.resolve(token);
|
||||
}
|
||||
});
|
||||
failedQueue = [];
|
||||
};
|
||||
|
||||
const ONLINE_LIBRARY_URL = "http://localhost:8080";
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: ONLINE_LIBRARY_URL + "/api/v1.0",
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
const apiAuth = axios.create({
|
||||
baseURL: ONLINE_LIBRARY_URL,
|
||||
timeout: 10000,
|
||||
withCredentials: true,
|
||||
@@ -11,6 +37,86 @@ const api = axios.create({
|
||||
},
|
||||
});
|
||||
|
||||
api.interceptors.request.use(
|
||||
async (config) => {
|
||||
const token = getAccessToken();
|
||||
if (token) {
|
||||
if (isTokenExpired(token)) {
|
||||
if (!isRefreshing) {
|
||||
isRefreshing = true;
|
||||
try {
|
||||
const response = await apiAuth.post("/reload");
|
||||
const newToken = response.data;
|
||||
setAccessToken(newToken);
|
||||
token = newToken;
|
||||
processQueue(null, newToken);
|
||||
} catch (error) {
|
||||
processQueue(error, null);
|
||||
removeAccessToken();
|
||||
return Promise.reject(error);
|
||||
} finally {
|
||||
isRefreshing = false;
|
||||
}
|
||||
} else {
|
||||
return new Promise((resolve, reject) => {
|
||||
failedQueue.push({ resolve, reject });
|
||||
})
|
||||
.then((token) => {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
return config;
|
||||
})
|
||||
.catch((error) => Promise.reject(error));
|
||||
}
|
||||
}
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
api.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error) => {
|
||||
const originalRequest = error.config;
|
||||
|
||||
if (error.response?.status === 401 && !originalRequest._retry) {
|
||||
originalRequest._retry = true;
|
||||
|
||||
if (!isRefreshing) {
|
||||
isRefreshing = true;
|
||||
try {
|
||||
const response = await apiAuth.post("/reload");
|
||||
const newToken = response.data;
|
||||
setAccessToken(newToken);
|
||||
processQueue(null, newToken);
|
||||
originalRequest.headers.Authorization = `Bearer ${newToken}`;
|
||||
return api(originalRequest);
|
||||
} catch (error) {
|
||||
processQueue(error, null);
|
||||
removeAccessToken();
|
||||
return Promise.reject(error);
|
||||
} finally {
|
||||
isRefreshing = false;
|
||||
}
|
||||
} else {
|
||||
return new Promise((resolve, reject) => {
|
||||
failedQueue.push({ resolve, reject });
|
||||
})
|
||||
.then((token) => {
|
||||
originalRequest.headers.Authorization = `Bearer ${token}`;
|
||||
return api(originalRequest);
|
||||
})
|
||||
.catch((error) => Promise.reject(error));
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export const bookAPI = {
|
||||
getAll: (params = {}) => {
|
||||
return api.get("/books", { params });
|
||||
@@ -31,3 +137,15 @@ export const bookAPI = {
|
||||
return api.delete("/book", { params: { id } });
|
||||
},
|
||||
};
|
||||
|
||||
export const authAPI = {
|
||||
login: (user, params = {}) => {
|
||||
return apiAuth.post("/login", user, { params });
|
||||
},
|
||||
registration: (user, params = {}) => {
|
||||
return apiAuth.post("/registration", user, { params });
|
||||
},
|
||||
logout: (params = {}) => {
|
||||
return apiAuth.get("/logout", { params });
|
||||
},
|
||||
};
|
||||
|
||||
18
online-library/src/ApiRequest/JwtUtils.js
Normal file
18
online-library/src/ApiRequest/JwtUtils.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import {jwtDecode} from "jwt-decode";
|
||||
|
||||
export const getAccessToken = () => localStorage.getItem("access_token");
|
||||
export const setAccessToken = (token) =>
|
||||
localStorage.setItem("access_token", token);
|
||||
export const removeAccessToken = () => localStorage.removeItem("access_token");
|
||||
|
||||
export const isTokenExpired = (token) => {
|
||||
if (!token) return true;
|
||||
try {
|
||||
const decodedToken = jwtDecode(token);
|
||||
const currentTime = Date.now() / 1000;
|
||||
return decodedToken.exp < currentTime;
|
||||
} catch (error) {
|
||||
console.error("Error decoding token:", error);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@@ -6,6 +6,7 @@ import { BrowserRouter, Route, Routes } from "react-router-dom";
|
||||
import Lk from "./Lk/Lk";
|
||||
import Choice from "./Choice/Choice";
|
||||
import BookCardInfo from "./BookCardInfo/BookCardIInfo";
|
||||
import AuthPages from "./Registration/AuthPages";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
@@ -18,6 +19,7 @@ function App() {
|
||||
<Route path="/choice" element={<Choice />}></Route>
|
||||
<Route path="/lk" element={<Lk />}></Route>
|
||||
<Route path="/infobook/:id" element={<BookCardInfo />}></Route>
|
||||
<Route path="/registration" element={<AuthPages></AuthPages>}></Route>
|
||||
</Routes>
|
||||
<Footer></Footer>
|
||||
</BrowserRouter>
|
||||
|
||||
@@ -3,18 +3,26 @@ import "bootstrap/dist/js/bootstrap.bundle.min";
|
||||
import "../style.css";
|
||||
import BookCard from "../BookCard/BookCard";
|
||||
import { useEffect, useState } from "react";
|
||||
import { bookAPI } from "../ApiRequest/ApiClient";
|
||||
import { authAPI, bookAPI } from "../ApiRequest/ApiClient";
|
||||
import { setAccessToken } from "../ApiRequest/JwtUtils";
|
||||
import { Spinner } from "react-bootstrap";
|
||||
|
||||
function Main() {
|
||||
const [bookData, setBookData] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const testUser = {
|
||||
username: "test21",
|
||||
password: "123456789",
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchBooks = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await bookAPI.getAll();
|
||||
setBookData(response.data);
|
||||
setBookData(response.data.content);
|
||||
} catch (error) {
|
||||
setError(error.message);
|
||||
} finally {
|
||||
@@ -23,7 +31,7 @@ function Main() {
|
||||
};
|
||||
fetchBooks();
|
||||
}, []);
|
||||
if (isLoading) return <div> Загрузка</div>;
|
||||
if (isLoading) return <Spinner animation="border" size="sm" />;
|
||||
if (error) return <div> Ошибка: {error}</div>;
|
||||
return (
|
||||
<div className="container">
|
||||
@@ -32,22 +40,6 @@ function Main() {
|
||||
{bookData.map((book) => (
|
||||
<BookCard book={book}></BookCard>
|
||||
))}
|
||||
{/* <BookCard
|
||||
title={"Джордж укыавяыав"}
|
||||
linkOnImage={"../resources/1984.jpg"}
|
||||
></BookCard>
|
||||
<BookCard
|
||||
title={"Джордж укыавяыав"}
|
||||
linkOnImage={"../resources/1984.jpg"}
|
||||
></BookCard>
|
||||
<BookCard
|
||||
title={"Джордж укыавяыав"}
|
||||
linkOnImage={"../resources/1984.jpg"}
|
||||
></BookCard>
|
||||
<BookCard
|
||||
title={"Джордж укыавяыав"}
|
||||
linkOnImage={"../resources/1984.jpg"}
|
||||
></BookCard> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
331
online-library/src/Registration/AuthPages.jsx
Normal file
331
online-library/src/Registration/AuthPages.jsx
Normal file
@@ -0,0 +1,331 @@
|
||||
import React, { use, useState } from "react";
|
||||
import {
|
||||
Container,
|
||||
Row,
|
||||
Col,
|
||||
Form,
|
||||
Button,
|
||||
Card,
|
||||
Tab,
|
||||
Tabs,
|
||||
Alert,
|
||||
} from "react-bootstrap";
|
||||
import "bootstrap/dist/css/bootstrap.css";
|
||||
import "bootstrap/dist/js/bootstrap.bundle.min";
|
||||
import { authAPI } from "../ApiRequest/ApiClient";
|
||||
import axios from "axios";
|
||||
import { setAccessToken } from "../ApiRequest/JwtUtils";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
const AuthPages = () => {
|
||||
const [activeTab, setActiveTab] = useState("login");
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: "var(--bg-dark)",
|
||||
minHeight: "100vh",
|
||||
padding: "20px 0",
|
||||
}}
|
||||
>
|
||||
<Container>
|
||||
<Row className="justify-content-center">
|
||||
<Col md={6} lg={5}>
|
||||
<Card
|
||||
style={{
|
||||
backgroundColor: "var(--bg-medium)",
|
||||
border: "none",
|
||||
borderRadius: "10px",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<Card.Body className="p-0">
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
onSelect={(k) => setActiveTab(k)}
|
||||
fill
|
||||
style={{
|
||||
backgroundColor: "var(--bg-dark)",
|
||||
}}
|
||||
>
|
||||
<Tab eventKey="login" title="Вход">
|
||||
<LoginForm />
|
||||
</Tab>
|
||||
<Tab eventKey="register" title="Регистрация">
|
||||
<RegisterForm />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const LoginForm = () => {
|
||||
const navigate = useNavigate();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [message, setMessage] = useState("");
|
||||
const [error, setError] = useState(null);
|
||||
const [formData, setFormData] = useState({
|
||||
username: "",
|
||||
password: "",
|
||||
});
|
||||
|
||||
const handleChange = (e) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
[e.target.name]: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
// Обработка входа
|
||||
console.log("Login data:", formData);
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await authAPI.login(formData);
|
||||
if (response.status == 200) {
|
||||
const token = response.data;
|
||||
setAccessToken(token);
|
||||
setTimeout(() => {
|
||||
navigate("/");
|
||||
}, 1500);
|
||||
}
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
setError(error.response.data.message);
|
||||
} else {
|
||||
setError("Ошибка сети. Повторите попытку чуть чуть попозже");
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ padding: "30px" }}>
|
||||
<h3 className="text-center mb-4" style={{ color: "var(--text-light)" }}>
|
||||
Вход в аккаунт
|
||||
</h3>
|
||||
{error && <Alert variant="danger">{error}</Alert>}
|
||||
{message && <Alert variant="success">{message}</Alert>}
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Label style={{ color: "var(--text-light)" }}>
|
||||
Email/Логин
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
name="username"
|
||||
value={formData.username}
|
||||
onChange={handleChange}
|
||||
required
|
||||
style={{
|
||||
backgroundColor: "var(--bg-dark)",
|
||||
border: "1px solid var(--accent)",
|
||||
color: "var(--text-light)",
|
||||
}}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group className="mb-4">
|
||||
<Form.Label style={{ color: "var(--text-light)" }}>Пароль</Form.Label>
|
||||
<Form.Control
|
||||
type="password"
|
||||
name="password"
|
||||
value={formData.password}
|
||||
onChange={handleChange}
|
||||
required
|
||||
style={{
|
||||
backgroundColor: "var(--bg-dark)",
|
||||
border: "1px solid var(--accent)",
|
||||
color: "var(--text-light)",
|
||||
}}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<div className="d-grid gap-2">
|
||||
<Button
|
||||
type="submit"
|
||||
style={{
|
||||
backgroundColor: "var(--accent)",
|
||||
border: "none",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
size="lg"
|
||||
>
|
||||
Войти
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const RegisterForm = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const [message, setMessage] = useState("");
|
||||
const [formData, setFormData] = useState({
|
||||
username: "",
|
||||
fio: "",
|
||||
email: "",
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
});
|
||||
|
||||
const handleChange = (e) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
[e.target.name]: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
console.log("Register data:", formData);
|
||||
setLoading(true);
|
||||
if (formData.password !== formData.confirmPassword) {
|
||||
setError("Пароли не совпадают!");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await authAPI.registration(formData);
|
||||
if (response.data) {
|
||||
setMessage("Регистрация прошла успешно!");
|
||||
setFormData({
|
||||
fio: "",
|
||||
email: "",
|
||||
username: "",
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
});
|
||||
setTimeout(() => {}, 2000);
|
||||
} else {
|
||||
setError(response.data.message || "Ошибка регистрации");
|
||||
}
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
setError(error.response.data.message);
|
||||
} else {
|
||||
setError("Ошибка сети. Попробуйте позже.");
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ padding: "30px" }}>
|
||||
<h3 className="text-center mb-4" style={{ color: "var(--text-light)" }}>
|
||||
Создать аккаунт
|
||||
</h3>
|
||||
{error && <Alert variant="danger">{error}</Alert>}
|
||||
{message && <Alert variant="success">{message}</Alert>}
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Label style={{ color: "var(--text-light)" }}>Логин</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
name="username"
|
||||
value={formData.username}
|
||||
onChange={handleChange}
|
||||
required
|
||||
style={{
|
||||
backgroundColor: "var(--bg-dark)",
|
||||
border: "1px solid var(--accent)",
|
||||
color: "var(--text-light)",
|
||||
}}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Label style={{ color: "var(--text-light)" }}>Имя</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
name="fio"
|
||||
value={formData.fio}
|
||||
onChange={handleChange}
|
||||
required
|
||||
style={{
|
||||
backgroundColor: "var(--bg-dark)",
|
||||
border: "1px solid var(--accent)",
|
||||
color: "var(--text-light)",
|
||||
}}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Label style={{ color: "var(--text-light)" }}>Email</Form.Label>
|
||||
<Form.Control
|
||||
type="email"
|
||||
name="email"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
required
|
||||
style={{
|
||||
backgroundColor: "var(--bg-dark)",
|
||||
border: "1px solid var(--accent)",
|
||||
color: "var(--text-light)",
|
||||
}}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Label style={{ color: "var(--text-light)" }}>Пароль</Form.Label>
|
||||
<Form.Control
|
||||
type="password"
|
||||
name="password"
|
||||
value={formData.password}
|
||||
onChange={handleChange}
|
||||
required
|
||||
style={{
|
||||
backgroundColor: "var(--bg-dark)",
|
||||
border: "1px solid var(--accent)",
|
||||
color: "var(--text-light)",
|
||||
}}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group className="mb-4">
|
||||
<Form.Label style={{ color: "var(--text-light)" }}>
|
||||
Подтвердите пароль
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
type="password"
|
||||
name="confirmPassword"
|
||||
value={formData.confirmPassword}
|
||||
onChange={handleChange}
|
||||
required
|
||||
style={{
|
||||
backgroundColor: "var(--bg-dark)",
|
||||
border: "1px solid var(--accent)",
|
||||
color: "var(--text-light)",
|
||||
}}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<div className="d-grid gap-2">
|
||||
<Button
|
||||
type="submit"
|
||||
style={{
|
||||
backgroundColor: "var(--accent)",
|
||||
border: "none",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
size="lg"
|
||||
>
|
||||
Зарегистрироваться
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthPages;
|
||||
@@ -2,11 +2,33 @@ import "bootstrap/dist/css/bootstrap.css";
|
||||
import "bootstrap/dist/js/bootstrap.bundle.min";
|
||||
import "../style.css";
|
||||
import BookCard from "../BookCard/BookCard";
|
||||
import { useEffect, useState } from "react";
|
||||
import { bookAPI } from "../ApiRequest/ApiClient";
|
||||
|
||||
function Search() {
|
||||
const [books, setBooks] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchBooks = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await bookAPI.getAll();
|
||||
setBooks(response.data.content);
|
||||
} catch (error) {
|
||||
setError(error.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchBooks();
|
||||
}, []);
|
||||
if (loading) return <div> Загрузка</div>;
|
||||
if (error) return <div> Ошибка: {error}</div>;
|
||||
return (
|
||||
<main className="container">
|
||||
<div className="mb-5 mx-auto" style={{maxWidth:"6000px"}}>
|
||||
<div className="mb-5 mx-auto" style={{ maxWidth: "6000px" }}>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-lg"
|
||||
@@ -15,7 +37,10 @@ function Search() {
|
||||
</div>
|
||||
|
||||
<div className="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
|
||||
<BookCard title={"rtjdxfgvhjksfd"} linkOnImage={"/resources/1984.jpg"}></BookCard>
|
||||
<BookCard
|
||||
title={"rtjdxfgvhjksfd"}
|
||||
linkOnImage={"/resources/1984.jpg"}
|
||||
></BookCard>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@ import "bootstrap/dist/js/bootstrap.bundle.min";
|
||||
import "../style.css";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { HashLink } from "react-router-hash-link";
|
||||
import { getAccessToken } from "../ApiRequest/JwtUtils";
|
||||
|
||||
function Headers() {
|
||||
return (
|
||||
@@ -70,8 +71,8 @@ function Headers() {
|
||||
</ul>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/lk" className="nav-link nav-link-custom">
|
||||
Личный кабинет
|
||||
<NavLink to={getAccessToken() ? "/lk" : "/registration"} className="nav-link nav-link-custom">
|
||||
{getAccessToken() ? "Личный кабинет" : "Вход/Регистрация"}
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -80,4 +80,30 @@ body {
|
||||
|
||||
.book-item:hover {
|
||||
transform: translateX(10px);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.nav-tabs .nav-link {
|
||||
color: var(--text-light);
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link.active {
|
||||
color: var(--accent);
|
||||
background-color: var(--bg-medium);
|
||||
border: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.nav-tabs {
|
||||
border-bottom: 1px solid var(--accent);
|
||||
}
|
||||
Reference in New Issue
Block a user