mvp для просмотра и загрузки фильмов
This commit is contained in:
355
package-lock.json
generated
355
package-lock.json
generated
@@ -8,12 +8,17 @@
|
|||||||
"name": "moovie-downloader",
|
"name": "moovie-downloader",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@popperjs/core": "^2.11.8",
|
||||||
"@testing-library/dom": "^10.4.1",
|
"@testing-library/dom": "^10.4.1",
|
||||||
"@testing-library/jest-dom": "^6.8.0",
|
"@testing-library/jest-dom": "^6.8.0",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"axios": "^1.11.0",
|
||||||
|
"bootstrap": "^5.3.8",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
|
"react-bootstrap": "^2.10.10",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
|
"react-router-dom": "^7.8.2",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
}
|
}
|
||||||
@@ -3072,6 +3077,85 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@popperjs/core": {
|
||||||
|
"version": "2.11.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||||
|
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"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==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.14.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/plugin-babel": {
|
"node_modules/@rollup/plugin-babel": {
|
||||||
"version": "5.3.1",
|
"version": "5.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
|
||||||
@@ -3420,6 +3504,15 @@
|
|||||||
"url": "https://github.com/sponsors/gregberge"
|
"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==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@testing-library/dom": {
|
"node_modules/@testing-library/dom": {
|
||||||
"version": "10.4.1",
|
"version": "10.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
|
||||||
@@ -3783,6 +3876,12 @@
|
|||||||
"integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==",
|
"integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"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==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/q": {
|
"node_modules/@types/q": {
|
||||||
"version": "1.5.8",
|
"version": "1.5.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.8.tgz",
|
||||||
@@ -3801,6 +3900,24 @@
|
|||||||
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
|
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react": {
|
||||||
|
"version": "19.1.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz",
|
||||||
|
"integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/resolve": {
|
"node_modules/@types/resolve": {
|
||||||
"version": "1.17.1",
|
"version": "1.17.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
|
||||||
@@ -3873,6 +3990,12 @@
|
|||||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"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==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/ws": {
|
"node_modules/@types/ws": {
|
||||||
"version": "8.18.1",
|
"version": "8.18.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
||||||
@@ -4891,6 +5014,33 @@
|
|||||||
"node": ">=4"
|
"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/axios/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/axobject-query": {
|
"node_modules/axobject-query": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||||
@@ -5276,6 +5426,25 @@
|
|||||||
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
|
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/bootstrap": {
|
||||||
|
"version": "5.3.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz",
|
||||||
|
"integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/twbs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/bootstrap"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@popperjs/core": "^2.11.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "1.1.12",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
@@ -5597,6 +5766,12 @@
|
|||||||
"integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==",
|
"integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"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==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/clean-css": {
|
"node_modules/clean-css": {
|
||||||
"version": "5.3.3",
|
"version": "5.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz",
|
||||||
@@ -6359,6 +6534,12 @@
|
|||||||
"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
|
"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/csstype": {
|
||||||
|
"version": "3.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/damerau-levenshtein": {
|
"node_modules/damerau-levenshtein": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
||||||
@@ -6685,6 +6866,16 @@
|
|||||||
"utila": "~0.4"
|
"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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.8.7",
|
||||||
|
"csstype": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dom-serializer": {
|
"node_modules/dom-serializer": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
|
||||||
@@ -9246,6 +9437,15 @@
|
|||||||
"node": ">= 0.4"
|
"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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ipaddr.js": {
|
"node_modules/ipaddr.js": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz",
|
||||||
@@ -13589,6 +13789,25 @@
|
|||||||
"react-is": "^16.13.1"
|
"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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"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==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/prop-types/node_modules/react-is": {
|
"node_modules/prop-types/node_modules/react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
@@ -13617,6 +13836,12 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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/psl": {
|
"node_modules/psl": {
|
||||||
"version": "1.15.0",
|
"version": "1.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
|
||||||
@@ -13770,6 +13995,37 @@
|
|||||||
"node": ">=14"
|
"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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"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": {
|
"node_modules/react-dev-utils": {
|
||||||
"version": "12.0.1",
|
"version": "12.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",
|
||||||
@@ -13899,6 +14155,12 @@
|
|||||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"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==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/react-refresh": {
|
"node_modules/react-refresh": {
|
||||||
"version": "0.11.0",
|
"version": "0.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
|
||||||
@@ -13908,6 +14170,53 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-router": {
|
||||||
|
"version": "7.8.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.8.2.tgz",
|
||||||
|
"integrity": "sha512-7M2fR1JbIZ/jFWqelpvSZx+7vd7UlBTfdZqf6OSdF9g6+sfdqJDAWcak6ervbHph200ePlu+7G8LdoiC3ReyAQ==",
|
||||||
|
"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/react-router-dom": {
|
||||||
|
"version": "7.8.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.8.2.tgz",
|
||||||
|
"integrity": "sha512-Z4VM5mKDipal2jQ385H6UBhiiEDlnJPx6jyWsTYoZQdl5TrjxEV2a9yl3Fi60NBJxYzOTGTTHXPi0pdizvTwow==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"react-router": "7.8.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=18",
|
||||||
|
"react-dom": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-router/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/react-scripts": {
|
"node_modules/react-scripts": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
|
||||||
@@ -13981,6 +14290,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"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": {
|
"node_modules/read-cache": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
@@ -14814,6 +15139,12 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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": {
|
"node_modules/set-function-length": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||||
@@ -16370,6 +16701,21 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"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": {
|
"node_modules/underscore": {
|
||||||
"version": "1.12.1",
|
"version": "1.12.1",
|
||||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz",
|
||||||
@@ -16622,6 +16968,15 @@
|
|||||||
"makeerror": "1.0.12"
|
"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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/watchpack": {
|
"node_modules/watchpack": {
|
||||||
"version": "2.4.4",
|
"version": "2.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz",
|
||||||
|
|||||||
@@ -3,12 +3,17 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@popperjs/core": "^2.11.8",
|
||||||
"@testing-library/dom": "^10.4.1",
|
"@testing-library/dom": "^10.4.1",
|
||||||
"@testing-library/jest-dom": "^6.8.0",
|
"@testing-library/jest-dom": "^6.8.0",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"axios": "^1.11.0",
|
||||||
|
"bootstrap": "^5.3.8",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
|
"react-bootstrap": "^2.10.10",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
|
"react-router-dom": "^7.Y8.2",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
name="description"
|
name="description"
|
||||||
content="Web site created using create-react-app"
|
content="Web site created using create-react-app"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
<!--
|
<!--
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
|
|||||||
28
src/App.js
28
src/App.js
@@ -1,25 +1,15 @@
|
|||||||
import logo from './logo.svg';
|
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||||
|
import './index.css';
|
||||||
|
import MainLayout from "./layouts/MainLayout";
|
||||||
|
import {ToastProvider} from "./contexts/ToastProvider";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<ToastProvider>
|
||||||
<header className="App-header">
|
<MainLayout/>
|
||||||
<img src={logo} className="App-logo" alt="logo" />
|
</ToastProvider>
|
||||||
<p>
|
);
|
||||||
Edit <code>src/App.js</code> and save to reload.
|
|
||||||
</p>
|
|
||||||
<a
|
|
||||||
className="App-link"
|
|
||||||
href="https://reactjs.org"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Learn React
|
|
||||||
</a>
|
|
||||||
</header>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
|
||||||
import App from './App';
|
|
||||||
|
|
||||||
test('renders learn react link', () => {
|
|
||||||
render(<App />);
|
|
||||||
const linkElement = screen.getByText(/learn react/i);
|
|
||||||
expect(linkElement).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
36
src/components/CardCompact.js
Normal file
36
src/components/CardCompact.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {Card} from 'react-bootstrap';
|
||||||
|
|
||||||
|
const descriptionDraw = (text) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{text} <br/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const CardCompact = ({item}) => {
|
||||||
|
console.log(item);
|
||||||
|
const image = item?.images?.length > 0 ? item.images[0]?.remoteUrl : null;
|
||||||
|
const rating = item.ratings?.imdb?.value ?? null;
|
||||||
|
const status = (item.monitored || item.hasFile) ? (
|
||||||
|
item.hasFile ? "Статус: Загружен" : "Статус: Загружается"
|
||||||
|
) : null;
|
||||||
|
return (
|
||||||
|
<Card className="mb-3" style={{width: '18rem'}}>
|
||||||
|
{/*<Card.Img variant="top" src={item.remotePoster}/>*/}
|
||||||
|
<Card.Img variant="top" src={image}/>
|
||||||
|
<Card.Body>
|
||||||
|
<Card.Title>{item.title}</Card.Title>
|
||||||
|
<Card.Text>
|
||||||
|
{descriptionDraw(`Год: ${item.year}`)}
|
||||||
|
{descriptionDraw(`Рейтинг: ${rating}`)}
|
||||||
|
{item.sizeOnDisk ? descriptionDraw(`Размер: ${(item.sizeOnDisk / 1000000000).toFixed(2)} Gb`) : null}
|
||||||
|
{status ? descriptionDraw(status) : null}
|
||||||
|
</Card.Text>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CardCompact;
|
||||||
32
src/components/CardExtend.js
Normal file
32
src/components/CardExtend.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {Button, Card, Col, Row} from 'react-bootstrap';
|
||||||
|
|
||||||
|
const CardExtended = ({item, selectHandle}) => {
|
||||||
|
const rating = item.ratings?.imdb?.value ?? null;
|
||||||
|
return (
|
||||||
|
<Card className="mb-3" style={{width: '100%'}} onClick={() => selectHandle(item)}>
|
||||||
|
<Row>
|
||||||
|
<Col md={3}>
|
||||||
|
<Card.Img variant="top" src={item.remotePoster} style={{marginTop: '1rem'}}/>
|
||||||
|
</Col>
|
||||||
|
<Col md={9}>
|
||||||
|
<Card.Body>
|
||||||
|
<Card.Title>{item.title}</Card.Title>
|
||||||
|
<Card.Text>
|
||||||
|
<strong>Год:</strong> {item.year}<br/>
|
||||||
|
<strong>Рейтинг:</strong> {rating}<br/>
|
||||||
|
{item.overview}
|
||||||
|
</Card.Text>
|
||||||
|
</Card.Body>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Col style={{justifyContent: 'end'}}>
|
||||||
|
<Button variant={'outline-primary'} title={"Download"}>Загрузить</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CardExtended;
|
||||||
31
src/components/Navbar.js
Normal file
31
src/components/Navbar.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Navbar, Container, Nav } from 'react-bootstrap';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
const NavbarComponent = () => {
|
||||||
|
return (
|
||||||
|
<Navbar bg="dark" variant="dark" expand="lg" sticky="top">
|
||||||
|
<Container>
|
||||||
|
<Navbar.Brand as={Link} to="/">
|
||||||
|
Media Tracker
|
||||||
|
</Navbar.Brand>
|
||||||
|
<Navbar.Toggle aria-controls="basic-navbar-nav" />
|
||||||
|
<Navbar.Collapse id="basic-navbar-nav">
|
||||||
|
<Nav className="me-auto">
|
||||||
|
<Nav.Link as={Link} to="/">
|
||||||
|
Главная
|
||||||
|
</Nav.Link>
|
||||||
|
<Nav.Link as={Link} to="/recommendations">
|
||||||
|
Рекомендации
|
||||||
|
</Nav.Link>
|
||||||
|
<Nav.Link as={Link} to="/search">
|
||||||
|
Поиск
|
||||||
|
</Nav.Link>
|
||||||
|
</Nav>
|
||||||
|
</Navbar.Collapse>
|
||||||
|
</Container>
|
||||||
|
</Navbar>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NavbarComponent;
|
||||||
144
src/components/RecommendationModal.js
Normal file
144
src/components/RecommendationModal.js
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
import React, {useEffect, useState} from 'react';
|
||||||
|
import {Button, Col, Container, FormCheck, FormSelect, Modal, Row} from 'react-bootstrap';
|
||||||
|
import axios from "axios";
|
||||||
|
import {useToast} from "../hooks/useToast";
|
||||||
|
|
||||||
|
const movieMonitor = [
|
||||||
|
{
|
||||||
|
id: "movieAndCollection",
|
||||||
|
name: "Все части"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "movieOnly",
|
||||||
|
name: "Только этот фильм"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const RecommendationModal = ({show, handleClose, item, serial, handleSave}) => {
|
||||||
|
const [movieQuality, setMovieQuality] = useState([])
|
||||||
|
const [quality, setQuality] = useState(null)
|
||||||
|
const [monitor, setMonitor] = useState(null)
|
||||||
|
const [film, setFilm] = useState(false)
|
||||||
|
const {addToast} = useToast();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
axios.get(`${process.env.REACT_APP_RADARR_HOST}/api/v3/qualityprofile`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'X-Api-Key': `${process.env.REACT_APP_RADARR_API_KEY}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((r) => {
|
||||||
|
setMovieQuality(r.data)
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
const request = !serial ? createMovieRequest : null;
|
||||||
|
request()
|
||||||
|
.then(res => {
|
||||||
|
axios.post(`${process.env.REACT_APP_RADARR_HOST}/api/v3/movie`, res,
|
||||||
|
{headers: {'X-Api-Key': `${process.env.REACT_APP_RADARR_API_KEY}`}})
|
||||||
|
.then(() => handleSave(item, serial))
|
||||||
|
.catch((err) => addToast(err, 'danger'));
|
||||||
|
})
|
||||||
|
.catch((err) => addToast(err, 'danger'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const createMovieRequest = async () => {
|
||||||
|
if(!monitor) {
|
||||||
|
// eslint-disable-next-line no-throw-literal
|
||||||
|
throw 'Проверьте пункт отслеживания';
|
||||||
|
}
|
||||||
|
if(!quality) {
|
||||||
|
// eslint-disable-next-line no-throw-literal
|
||||||
|
throw 'Необходимо указать качество';
|
||||||
|
}
|
||||||
|
let request = item;
|
||||||
|
request.id = 0;
|
||||||
|
request.monitored = true;
|
||||||
|
request.qualityProfileId = quality;
|
||||||
|
request.minimumAvailability = "released"
|
||||||
|
request.addOptions = {
|
||||||
|
monitor: monitor,
|
||||||
|
searchForMovie: true
|
||||||
|
}
|
||||||
|
const folders = await axios.get(`${process.env.REACT_APP_RADARR_HOST}/api/v3/rootFolder`,
|
||||||
|
{headers: {'X-Api-Key': `${process.env.REACT_APP_RADARR_API_KEY}`}});
|
||||||
|
request.rootFolderPath = folders.data.find((d) => d.path.includes(film ? "film" : "mult")).path;
|
||||||
|
|
||||||
|
const tags = await axios.get(`${process.env.REACT_APP_RADARR_HOST}/api/v3/tag`,
|
||||||
|
{headers: {'X-Api-Key': `${process.env.REACT_APP_RADARR_API_KEY}`}});
|
||||||
|
request.tags = tags.data.filter((t) => t.label === (film ? "film" : "mult")).map((t) => t.id)
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!item) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal show={show} onHide={handleClose} fullscreen={true}>
|
||||||
|
<Modal.Header closeButton>
|
||||||
|
<Modal.Title>{item.title}</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body>
|
||||||
|
<Container fluid='xl'>
|
||||||
|
<Row>
|
||||||
|
<Col sm={{span: 3}}>
|
||||||
|
<img width="100%" className='mb-3' src={item.remotePoster} alt={item.title}/>
|
||||||
|
</Col>
|
||||||
|
<div className="col-9 container text-center">
|
||||||
|
<Row>
|
||||||
|
<div className="col">
|
||||||
|
<p>{item.overview}</p>
|
||||||
|
</div>
|
||||||
|
</Row>
|
||||||
|
<Row style={{width: '60%'}}>
|
||||||
|
<Col>
|
||||||
|
<p style={{marginRight: '1rem'}}>Мультик</p>
|
||||||
|
<FormCheck type='switch' disabled={serial} style={{marginBottom: '1rem'}}
|
||||||
|
onChange={() => setFilm(!film)}/>
|
||||||
|
<p>Фильм</p>
|
||||||
|
<FormCheck type='checkbox' disabled={!serial}
|
||||||
|
style={{marginBottom: '1rem', marginLeft: '1rem'}}
|
||||||
|
label="Сериал"/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<p style={{width: '50%', paddingTop: '1rem'}}>Что отслеживать</p>
|
||||||
|
<FormSelect onChange={(e) => setMonitor(e.target.value)}>
|
||||||
|
<option selected value={null}>Выберите пункт из меню</option>
|
||||||
|
{movieMonitor.map((m) => <option value={m.id} key={m.id}>{m.name}</option>)}
|
||||||
|
</FormSelect>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<p style={{width: '50%', paddingTop: '1rem'}}>Качество</p>
|
||||||
|
<FormSelect onChange={(e) => setQuality(parseInt(e.target.value))}>
|
||||||
|
<option selected value={null}>Выберете качество</option>
|
||||||
|
{movieQuality.map((quality) => <option value={quality.id}
|
||||||
|
key={quality.id}>{quality.name}</option>)}
|
||||||
|
</FormSelect>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Button variant={'primary'} onClick={() => handleSubmit()}>Скачать</Button>
|
||||||
|
</Container>
|
||||||
|
</Modal.Body>
|
||||||
|
<Modal.Footer>
|
||||||
|
<Button variant="secondary" onClick={() => handleClose()}>
|
||||||
|
Закрыть
|
||||||
|
</Button>
|
||||||
|
</Modal.Footer>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RecommendationModal;
|
||||||
16
src/components/SceletonCompact.js
Normal file
16
src/components/SceletonCompact.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
export function SceletonCompact() {
|
||||||
|
return (
|
||||||
|
<div className="card" aria-hidden="true" style={{width:'18rem', height: '30rem'}}>
|
||||||
|
<div className="card-img-top" style={{background: 'grey', width: '18rem', height: '30rem'}} />
|
||||||
|
<div className="card-body">
|
||||||
|
<h5 className="card-title placeholder-glow">
|
||||||
|
<span className="placeholder col-6"></span>
|
||||||
|
</h5>
|
||||||
|
<p className="card-text placeholder-glow">
|
||||||
|
<span className="placeholder col-8"></span>
|
||||||
|
<span className="placeholder col-8"></span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
47
src/contexts/ToastProvider.js
Normal file
47
src/contexts/ToastProvider.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import {Toast, ToastContainer} from "react-bootstrap";
|
||||||
|
import {useState} from "react";
|
||||||
|
|
||||||
|
export const ToastContext = React.createContext(undefined);
|
||||||
|
|
||||||
|
export function ToastProvider({children}) {
|
||||||
|
const [toasts, setToasts] = useState([]);
|
||||||
|
|
||||||
|
// Функция для добавления нового тоста
|
||||||
|
const addToast = (message, variant = 'secondary') => {
|
||||||
|
const newToast = {
|
||||||
|
id: Date.now(), // Уникальный идентификатор
|
||||||
|
message,
|
||||||
|
variant,
|
||||||
|
};
|
||||||
|
|
||||||
|
setToasts([newToast, ...toasts]);
|
||||||
|
|
||||||
|
// Удаляем тост через 3 секунды
|
||||||
|
setTimeout(() => {
|
||||||
|
setToasts(toasts.filter(t => t.id !== newToast.id));
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToastContext.Provider value={{ addToast }}>
|
||||||
|
{children}
|
||||||
|
<ToastContainer position="bottom-end">
|
||||||
|
{toasts.map(toast => (
|
||||||
|
<Toast
|
||||||
|
key={toast.id}
|
||||||
|
bg={toast.variant}
|
||||||
|
aria-atomic={true}
|
||||||
|
className="rounded me-2 mb-1 me-1 border-0"
|
||||||
|
autohide
|
||||||
|
delay={3000}
|
||||||
|
>
|
||||||
|
<Toast.Body className="text-white">
|
||||||
|
{toast.message}
|
||||||
|
</Toast.Body>
|
||||||
|
</Toast>
|
||||||
|
))}
|
||||||
|
</ToastContainer>
|
||||||
|
</ToastContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
19
src/hooks/useAxios.js
Normal file
19
src/hooks/useAxios.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const useAxios = (url) => {
|
||||||
|
const [data, setData] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
axios.get(url, )
|
||||||
|
.then((r) => setData(r.data))
|
||||||
|
.catch((err) => setError(err))
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
}, [url]);
|
||||||
|
|
||||||
|
return { data, loading, error };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useAxios;
|
||||||
12
src/hooks/useToast.js
Normal file
12
src/hooks/useToast.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import {useContext} from "react";
|
||||||
|
import {ToastContext} from "../contexts/ToastProvider";
|
||||||
|
|
||||||
|
export function useToast() {
|
||||||
|
const context = useContext(ToastContext);
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useToast must be used within a ToastProvider');
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
@@ -11,3 +11,10 @@ code {
|
|||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||||
monospace;
|
monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.col {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|||||||
14
src/index.js
14
src/index.js
@@ -1,17 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import './index.css';
|
// import './index.css';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import reportWebVitals from './reportWebVitals';
|
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<App/>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
|
||||||
// If you want to start measuring performance in your app, pass a function
|
|
||||||
// to log results (for example: reportWebVitals(console.log))
|
|
||||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
|
||||||
reportWebVitals();
|
|
||||||
|
|||||||
23
src/layouts/MainLayout.js
Normal file
23
src/layouts/MainLayout.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {BrowserRouter as Router, Route, Routes} from 'react-router-dom';
|
||||||
|
import Navbar from "../components/Navbar";
|
||||||
|
import {Recommendation} from "../pages/RecommendationPage";
|
||||||
|
import {Search} from "../pages/SearchPage";
|
||||||
|
import {NotFound} from "../pages/NotFoundPage";
|
||||||
|
import Home from "../pages/HomePage";
|
||||||
|
|
||||||
|
const MainLayout = () => {
|
||||||
|
return (
|
||||||
|
<Router>
|
||||||
|
<Navbar/>
|
||||||
|
<Routes>
|
||||||
|
<Route exact path="/" element={<Home/>}/>
|
||||||
|
<Route path="/recommendations" element={<Recommendation/>}/>
|
||||||
|
<Route path="/search" element={<Search/>}/>
|
||||||
|
<Route path="*" element={<NotFound/>}/>
|
||||||
|
</Routes>
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MainLayout;
|
||||||
64
src/pages/HomePage.js
Normal file
64
src/pages/HomePage.js
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import React, {useEffect, useState} from 'react';
|
||||||
|
import {Accordion, Badge, Col, Container, Row} from "react-bootstrap";
|
||||||
|
import CardCompact from "../components/CardCompact";
|
||||||
|
import axios from "axios";
|
||||||
|
import {useToast} from "../hooks/useToast";
|
||||||
|
import {SceletonCompact} from "../components/SceletonCompact";
|
||||||
|
|
||||||
|
const Home = () => {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [movies, setMovies] = useState([]);
|
||||||
|
// const [series, setSeries] = useState([]);
|
||||||
|
const {addToast} = useToast();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLoading(true);
|
||||||
|
axios.get(`${process.env.REACT_APP_RADARR_HOST}/api/v3/movie`,
|
||||||
|
{headers: {'X-Api-Key': process.env.REACT_APP_RADARR_API_KEY}})
|
||||||
|
.then((r) => setMovies(r.data))
|
||||||
|
.catch((err) => addToast(err, 'alert'))
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Accordion defaultActiveKey={["0"]} alwaysOpen>
|
||||||
|
<Accordion.Item eventKey="0">
|
||||||
|
<Accordion.Header className="mt-3">
|
||||||
|
Фильмы
|
||||||
|
</Accordion.Header>
|
||||||
|
<Accordion.Body>
|
||||||
|
<Row className='row-cols-auto'>
|
||||||
|
{loading ?
|
||||||
|
(
|
||||||
|
// Отображаем 10 скелетонов
|
||||||
|
Array.from({length: 10}, (_, index) => (
|
||||||
|
<Col key={`skeleton-${index}`}>
|
||||||
|
<SceletonCompact key={index} width={300} height={400}/>
|
||||||
|
</Col>
|
||||||
|
))
|
||||||
|
)
|
||||||
|
: movies.map(rec => (
|
||||||
|
<Col key={`col-${rec.title}-${rec.year}`}>
|
||||||
|
<CardCompact item={rec} key={`${rec.title}-${rec.year}`}/>
|
||||||
|
</Col>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
</Accordion.Body>
|
||||||
|
</Accordion.Item>
|
||||||
|
<Accordion.Item eventKey={"1"}>
|
||||||
|
<Accordion.Header className="mt-5">Сериалы</Accordion.Header>
|
||||||
|
<Accordion.Body>
|
||||||
|
<Row>
|
||||||
|
<Badge bg="info">Функционал для сериалов пока не реализован</Badge>
|
||||||
|
</Row>
|
||||||
|
</Accordion.Body>
|
||||||
|
</Accordion.Item>
|
||||||
|
</Accordion>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Home;
|
||||||
|
|
||||||
25
src/pages/NotFoundPage.js
Normal file
25
src/pages/NotFoundPage.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Container, Row, Col } from 'react-bootstrap';
|
||||||
|
|
||||||
|
export function NotFound () {
|
||||||
|
return (
|
||||||
|
<Container className="py-5">
|
||||||
|
<Row className="justify-content-center align-items-center">
|
||||||
|
<Col md={6} className="text-center">
|
||||||
|
<div className="mb-4">
|
||||||
|
<h1 className="display-4 fw-bold text-danger">404</h1>
|
||||||
|
<h2 className="mb-4">Кажется, что-то пошло не так...</h2>
|
||||||
|
</div>
|
||||||
|
<p className="lead">
|
||||||
|
Страница, которую вы ищете, не существует или была перемещена.
|
||||||
|
</p>
|
||||||
|
<div className="mt-4">
|
||||||
|
<a href="/" className="btn btn-primary">
|
||||||
|
Вернуться на главную
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
67
src/pages/RecommendationPage.js
Normal file
67
src/pages/RecommendationPage.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import React, {useEffect, useState} from 'react';
|
||||||
|
import axios from "axios";
|
||||||
|
import CardCompact from "../components/CardCompact";
|
||||||
|
import {SceletonCompact} from "../components/SceletonCompact";
|
||||||
|
import RecommendationModal from "../components/RecommendationModal";
|
||||||
|
import {Col, Container, Row} from "react-bootstrap";
|
||||||
|
import {useToast} from "../hooks/useToast";
|
||||||
|
|
||||||
|
export function Recommendation() {
|
||||||
|
const [recommendations, setRecommendations] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [modalView, setModalView] = useState(false);
|
||||||
|
const [item, setItem] = useState({});
|
||||||
|
const {addToast} = useToast();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLoading(true);
|
||||||
|
axios.get(`${process.env.REACT_APP_RADARR_HOST}/api/v3/importlist/movie?includeRecommendations=true&includeTrending=true&includePopular=true`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'X-Api-Key': `${process.env.REACT_APP_RADARR_API_KEY}`
|
||||||
|
}
|
||||||
|
}).then((r) => setRecommendations(r.data))
|
||||||
|
.catch((err) => console.log(err))
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function handleModal(item) {
|
||||||
|
setModalView(true);
|
||||||
|
setItem(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCloseModal = () => {
|
||||||
|
setModalView(false);
|
||||||
|
setItem({})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDownload = (item) => {
|
||||||
|
const nState = recommendations.filter(r => r.title !== item.title && r.year !== item.year);
|
||||||
|
setRecommendations(nState);
|
||||||
|
addToast(`${item.title} добавлен в список загрузок`, 'success');
|
||||||
|
handleCloseModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className="text-center">
|
||||||
|
<h2 className="mt-3">Рекомендации</h2>
|
||||||
|
<Row className='row-cols-auto'>
|
||||||
|
{loading ?
|
||||||
|
(
|
||||||
|
// Отображаем 10 скелетонов
|
||||||
|
Array.from({length: 10}, (_, index) => (
|
||||||
|
<Col key={`skeleton-${index}`}>
|
||||||
|
<SceletonCompact key={index} width={300} height={400}/>
|
||||||
|
</Col>
|
||||||
|
))
|
||||||
|
)
|
||||||
|
: recommendations.map(rec => (
|
||||||
|
<Col key={`col-${rec.title}-${rec.year}`} onClick={() => handleModal(rec)}>
|
||||||
|
<CardCompact item={rec} key={`${rec.title}-${rec.year}`}/>
|
||||||
|
</Col>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
<RecommendationModal show={modalView} handleClose={handleCloseModal} item={item} handleSave={handleDownload}/>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
105
src/pages/SearchPage.js
Normal file
105
src/pages/SearchPage.js
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import React, {useEffect, useMemo, useState} from 'react';
|
||||||
|
import axios from "axios";
|
||||||
|
import CardExtend from "../components/CardExtend";
|
||||||
|
import {Alert, Container, InputGroup, Row} from "react-bootstrap";
|
||||||
|
import RecommendationModal from "../components/RecommendationModal";
|
||||||
|
import {useToast} from "../hooks/useToast";
|
||||||
|
|
||||||
|
export function Search() {
|
||||||
|
const [query, setQuery] = useState('');
|
||||||
|
const [loadMovies, setLoadMovies] = useState(false);
|
||||||
|
// const [loadTv, setLoadTv] = useState(false);
|
||||||
|
const [errorMovies, setErrorMovies] = useState(false);
|
||||||
|
// const [errorTv, setErrorTv] = useState(false);
|
||||||
|
// const [resultTv, setResultTv] = useState([]);
|
||||||
|
const [resultMovies, setResultMovies] = useState([]);
|
||||||
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
const [selectItem, setSelectItem] = useState({});
|
||||||
|
const {addToast} = useToast();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (query.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoadMovies(true);
|
||||||
|
axios.get(`${process.env.REACT_APP_RADARR_HOST}/api/v3/movie/lookup?term=${query}}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
// "Content-Type": "application/json",
|
||||||
|
'X-Api-Key': `${process.env.REACT_APP_RADARR_API_KEY}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((r) => setResultMovies(r.data))
|
||||||
|
.catch((err) => setErrorMovies(err))
|
||||||
|
.finally(() => setLoadMovies(false));
|
||||||
|
|
||||||
|
}, [query]);
|
||||||
|
|
||||||
|
const handleChange = (item) => {
|
||||||
|
setSelectItem(item);
|
||||||
|
setShowModal(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = useMemo(() => {
|
||||||
|
let array = resultMovies;
|
||||||
|
// array.concat(resultTv);
|
||||||
|
return array.sort((a, b) => a.title.localeCompare(b.title));
|
||||||
|
}, [resultMovies]);
|
||||||
|
|
||||||
|
const handleCloseModal = () => {
|
||||||
|
setShowModal(false);
|
||||||
|
setSelectItem(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSave = (item, serial) => {
|
||||||
|
let nState = resultMovies;
|
||||||
|
nState = nState.filter(i => i.title !== item.title && i.year !== item.year);
|
||||||
|
|
||||||
|
const func = setResultMovies;
|
||||||
|
func(nState);
|
||||||
|
addToast(`${item.title} добавлен в список загрузок`, 'success');
|
||||||
|
handleCloseModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Используем хук useAxios для обработки запросов
|
||||||
|
// const {response, loading, errorTv} = useAxios('/api/search', 'get', {query});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container style={{marginTop: 5}}>
|
||||||
|
<h2>Поиск</h2>
|
||||||
|
<form className="mb-4">
|
||||||
|
<InputGroup>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={query}
|
||||||
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
|
className="form-control"
|
||||||
|
placeholder="Введите название"
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{loadMovies && <div className="text-center">Загрузка...</div>}
|
||||||
|
|
||||||
|
{errorMovies && (
|
||||||
|
<Alert variant="danger">{errorMovies}</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{result && result.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<h3>Результаты поиска</h3>
|
||||||
|
<Row>
|
||||||
|
{resultMovies.map(result => <CardExtend item={result} key={result.id} selectHandle={handleChange}/>)}
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(!loadMovies && !errorMovies && (!resultMovies || !result.length)) && (
|
||||||
|
<Alert variant='info'>
|
||||||
|
Ничего не найдено
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
<RecommendationModal item={selectItem} handleClose={handleCloseModal} show={showModal} serial={false} handleSave={handleSave}/>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
const reportWebVitals = onPerfEntry => {
|
|
||||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
|
||||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
|
||||||
getCLS(onPerfEntry);
|
|
||||||
getFID(onPerfEntry);
|
|
||||||
getFCP(onPerfEntry);
|
|
||||||
getLCP(onPerfEntry);
|
|
||||||
getTTFB(onPerfEntry);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default reportWebVitals;
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
|
||||||
// allows you to do things like:
|
|
||||||
// expect(element).toHaveTextContent(/react/i)
|
|
||||||
// learn more: https://github.com/testing-library/jest-dom
|
|
||||||
import '@testing-library/jest-dom';
|
|
||||||
Reference in New Issue
Block a user