diff --git a/.eslintrc b/.eslintrc index 48aab946..20632049 100644 --- a/.eslintrc +++ b/.eslintrc @@ -17,6 +17,8 @@ "ignorePatterns": ["src/service*.ts"], "rules": { "complexity": "off", - "@typescript-eslint/no-unnecessary-type-assertion": "off" + "@typescript-eslint/no-unnecessary-type-assertion": "off", + "@typescript-eslint/no-unsafe-return": "off", + "@typescript-eslint/no-unsafe-call": "off" } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 9eff9ec1..0bf303b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Changed * [#567](https://github.com/shlinkio/shlink-web-client/pull/567) Improved Shlink 3.0.0 compatibility by checking the `INVALID_SHORT_URL_DELETION` error code when deleting short URLs. +* [#524](https://github.com/shlinkio/shlink-web-client/pull/524) Updated to react-router v6. ### Deprecated * *Nothing* diff --git a/package-lock.json b/package-lock.json index edbb0d32..104c475f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,7 @@ "react-external-link": "^1.2.0", "react-leaflet": "^3.1.0", "react-redux": "^7.2.2", - "react-router-dom": "^5.2.0", + "react-router-dom": "^6.2.1", "react-swipeable": "^6.0.1", "react-tag-autocomplete": "^6.1.0", "reactstrap": "^8.9.0", @@ -71,7 +71,6 @@ "@types/react-dom": "^17.0.1", "@types/react-leaflet": "^2.5.2", "@types/react-redux": "^7.1.16", - "@types/react-router-dom": "^5.1.7", "@types/react-tag-autocomplete": "^6.1.0", "@types/uuid": "^8.3.0", "@wojtekmaj/enzyme-adapter-react-17": "^0.6.5", @@ -2194,11 +2193,14 @@ } }, "node_modules/@babel/runtime": { - "version": "7.2.0", - "resolved": "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.2.0.tgz", - "integrity": "sha1-sD5C7t31iY4AZG5MhA+ge6jcrX8=", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.0.tgz", + "integrity": "sha512-etcO/ohMNaNA2UBdaXBBSX/3aEzFMRrVfaPv8Ptc0k+cWpWW0QFiGZ2XnVqQZI1Cf734LbPGmqBKWESfW4x/dQ==", "dependencies": { - "regenerator-runtime": "^0.12.0" + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/runtime-corejs3": { @@ -2211,12 +2213,6 @@ "regenerator-runtime": "^0.13.4" } }, - "node_modules/@babel/runtime-corejs3/node_modules/regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - }, "node_modules/@babel/template": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz", @@ -4408,12 +4404,6 @@ "@types/node": "*" } }, - "node_modules/@types/history": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.8.tgz", - "integrity": "sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==", - "dev": true - }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", @@ -4619,27 +4609,6 @@ "redux": "^4.0.0" } }, - "node_modules/@types/react-router": { - "version": "5.1.11", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.11.tgz", - "integrity": "sha512-ofHbZMlp0Y2baOHgsWBQ4K3AttxY61bDMkwTiBOkPg7U6C/3UwwB5WaIx28JmSVi/eX3uFEMRo61BV22fDQIvg==", - "dev": true, - "dependencies": { - "@types/history": "*", - "@types/react": "*" - } - }, - "node_modules/@types/react-router-dom": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.7.tgz", - "integrity": "sha512-D5mHD6TbdV/DNHYsnwBTv+y73ei+mMjrkGrla86HthE4/PVvL1J94Bu3qABU+COXzpL23T1EZapVVpwHuBXiUg==", - "dev": true, - "dependencies": { - "@types/history": "*", - "@types/react": "*", - "@types/react-router": "*" - } - }, "node_modules/@types/react-tag-autocomplete": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/react-tag-autocomplete/-/react-tag-autocomplete-6.1.0.tgz", @@ -5639,21 +5608,6 @@ "node": ">=6.0" } }, - "node_modules/aria-query/node_modules/@babel/runtime": { - "version": "7.13.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", - "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.13.4" - } - }, - "node_modules/aria-query/node_modules/regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - }, "node_modules/arr-diff": { "version": "4.0.0", "resolved": "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz", @@ -6757,15 +6711,6 @@ "resolve": "^1.12.0" } }, - "node_modules/babel-plugin-macros/node_modules/@babel/runtime": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", - "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.13.4" - } - }, "node_modules/babel-plugin-macros/node_modules/cosmiconfig": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", @@ -6822,12 +6767,6 @@ "node": ">=8" } }, - "node_modules/babel-plugin-macros/node_modules/regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - }, "node_modules/babel-plugin-macros/node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -7895,12 +7834,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/babel-preset-react-app/node_modules/regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - }, "node_modules/babel-preset-react-app/node_modules/regenerator-transform": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", @@ -12429,27 +12362,12 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7" } }, - "node_modules/eslint-plugin-jsx-a11y/node_modules/@babel/runtime": { - "version": "7.13.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", - "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.13.4" - } - }, "node_modules/eslint-plugin-jsx-a11y/node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, - "node_modules/eslint-plugin-jsx-a11y/node_modules/regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - }, "node_modules/eslint-plugin-node": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", @@ -15454,16 +15372,11 @@ "dev": true }, "node_modules/history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/history/-/history-5.2.0.tgz", + "integrity": "sha512-uPSF6lAJb3nSePJ43hN3eKj1dTWpN9gMod0ZssbFTIsen+WehTmEadgL+kg78xLJFdRfrrC//SavDzmRVdE+Ig==", "dependencies": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" + "@babel/runtime": "^7.7.6" } }, "node_modules/hmac-drbg": { @@ -19379,32 +19292,6 @@ "node": ">=4" } }, - "node_modules/mini-create-react-context": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz", - "integrity": "sha512-b0TytUgFSbgFJGzJqXPKCFCBWigAjpjo+Fl7Vf7ZbKRDptszpppKxXH6DRXEABZ/gcEQczeb0iZ7JvL8e8jjCA==", - "dependencies": { - "@babel/runtime": "^7.5.5", - "tiny-warning": "^1.0.3" - }, - "peerDependencies": { - "prop-types": "^15.0.0", - "react": "^0.14.0 || ^15.0.0 || ^16.0.0" - } - }, - "node_modules/mini-create-react-context/node_modules/@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "dependencies": { - "regenerator-runtime": "^0.13.4" - } - }, - "node_modules/mini-create-react-context/node_modules/regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - }, "node_modules/mini-css-extract-plugin": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.3.1.tgz", @@ -21113,6 +21000,7 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, "dependencies": { "isarray": "0.0.1" } @@ -21120,7 +21008,8 @@ "node_modules/path-to-regexp/node_modules/isarray": { "version": "0.0.1", "resolved": "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true }, "node_modules/path-type": { "version": "4.0.0", @@ -26602,12 +26491,6 @@ "asap": "~2.0.6" } }, - "node_modules/react-app-polyfill/node_modules/regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - }, "node_modules/react-chartjs-2": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-3.0.4.tgz", @@ -27022,59 +26905,33 @@ } } }, - "node_modules/react-redux/node_modules/@babel/runtime": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", - "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", - "dependencies": { - "regenerator-runtime": "^0.13.4" - } - }, "node_modules/react-redux/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/react-redux/node_modules/regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - }, "node_modules/react-router": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", - "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.2.1.tgz", + "integrity": "sha512-2fG0udBtxou9lXtK97eJeET2ki5//UWfQSl1rlJ7quwe6jrktK9FCCc8dQb5QY6jAv3jua8bBQRhhDOM/kVRsg==", "dependencies": { - "@babel/runtime": "^7.1.2", - "history": "^4.9.0", - "hoist-non-react-statics": "^3.1.0", - "loose-envify": "^1.3.1", - "mini-create-react-context": "^0.4.0", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.2", - "react-is": "^16.6.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" + "history": "^5.2.0" }, "peerDependencies": { - "react": ">=15" + "react": ">=16.8" } }, "node_modules/react-router-dom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", - "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.2.1.tgz", + "integrity": "sha512-I6Zax+/TH/cZMDpj3/4Fl2eaNdcvoxxHoH1tYOREsQ22OKDYofGebrNm6CTPUcvLvZm63NL/vzCYdjf9CUhqmA==", "dependencies": { - "@babel/runtime": "^7.1.2", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.2.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" + "history": "^5.2.0", + "react-router": "6.2.1" }, "peerDependencies": { - "react": ">=15" + "react": ">=16.8", + "react-dom": ">=16.8" } }, "node_modules/react-shallow-renderer": { @@ -27156,19 +27013,6 @@ "react-dom": ">=16.3.0" } }, - "node_modules/reactstrap/node_modules/@babel/runtime": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.8.tgz", - "integrity": "sha512-CwQljpw6qSayc0fRG1soxHAKs1CnQMOChm4mlQP6My0kf9upVGizj/KhlTTgyUnETmHpcUXjaluNAkteRFuafg==", - "dependencies": { - "regenerator-runtime": "^0.13.4" - } - }, - "node_modules/reactstrap/node_modules/regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - }, "node_modules/read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -27381,9 +27225,9 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.12.1", - "resolved": "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", - "integrity": "sha1-+hpxVEdkwDb4xJsToIsllMn4oN4=" + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, "node_modules/regenerator-transform": { "version": "0.14.5", @@ -27394,21 +27238,6 @@ "@babel/runtime": "^7.8.4" } }, - "node_modules/regenerator-transform/node_modules/@babel/runtime": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.8.tgz", - "integrity": "sha512-CwQljpw6qSayc0fRG1soxHAKs1CnQMOChm4mlQP6My0kf9upVGizj/KhlTTgyUnETmHpcUXjaluNAkteRFuafg==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.13.4" - } - }, - "node_modules/regenerator-transform/node_modules/regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - }, "node_modules/regex-not": { "version": "1.0.2", "resolved": "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz", @@ -27880,11 +27709,6 @@ "node": ">=4" } }, - "node_modules/resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" - }, "node_modules/resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -31802,16 +31626,6 @@ "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", "dev": true }, - "node_modules/tiny-invariant": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", - "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" - }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" - }, "node_modules/tinycolor2": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz", @@ -33068,11 +32882,6 @@ "spdx-expression-parse": "^3.0.0" } }, - "node_modules/value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -34384,15 +34193,6 @@ "node": ">=10.0.0" } }, - "node_modules/workbox-build/node_modules/@babel/runtime": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.0.tgz", - "integrity": "sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.13.4" - } - }, "node_modules/workbox-build/node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -34411,12 +34211,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/workbox-build/node_modules/regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - }, "node_modules/workbox-build/node_modules/source-map": { "version": "0.8.0-beta.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", @@ -36554,11 +36348,11 @@ } }, "@babel/runtime": { - "version": "7.2.0", - "resolved": "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.2.0.tgz", - "integrity": "sha1-sD5C7t31iY4AZG5MhA+ge6jcrX8=", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.0.tgz", + "integrity": "sha512-etcO/ohMNaNA2UBdaXBBSX/3aEzFMRrVfaPv8Ptc0k+cWpWW0QFiGZ2XnVqQZI1Cf734LbPGmqBKWESfW4x/dQ==", "requires": { - "regenerator-runtime": "^0.12.0" + "regenerator-runtime": "^0.13.4" } }, "@babel/runtime-corejs3": { @@ -36569,14 +36363,6 @@ "requires": { "core-js-pure": "^3.0.0", "regenerator-runtime": "^0.13.4" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - } } }, "@babel/template": { @@ -38204,12 +37990,6 @@ "@types/node": "*" } }, - "@types/history": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.8.tgz", - "integrity": "sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==", - "dev": true - }, "@types/hoist-non-react-statics": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", @@ -38415,27 +38195,6 @@ "redux": "^4.0.0" } }, - "@types/react-router": { - "version": "5.1.11", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.11.tgz", - "integrity": "sha512-ofHbZMlp0Y2baOHgsWBQ4K3AttxY61bDMkwTiBOkPg7U6C/3UwwB5WaIx28JmSVi/eX3uFEMRo61BV22fDQIvg==", - "dev": true, - "requires": { - "@types/history": "*", - "@types/react": "*" - } - }, - "@types/react-router-dom": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.7.tgz", - "integrity": "sha512-D5mHD6TbdV/DNHYsnwBTv+y73ei+mMjrkGrla86HthE4/PVvL1J94Bu3qABU+COXzpL23T1EZapVVpwHuBXiUg==", - "dev": true, - "requires": { - "@types/history": "*", - "@types/react": "*", - "@types/react-router": "*" - } - }, "@types/react-tag-autocomplete": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/react-tag-autocomplete/-/react-tag-autocomplete-6.1.0.tgz", @@ -39224,23 +38983,6 @@ "requires": { "@babel/runtime": "^7.10.2", "@babel/runtime-corejs3": "^7.10.2" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.13.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", - "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - } } }, "arr-diff": { @@ -40062,15 +39804,6 @@ "resolve": "^1.12.0" }, "dependencies": { - "@babel/runtime": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", - "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, "cosmiconfig": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", @@ -40112,12 +39845,6 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -40980,12 +40707,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - }, "regenerator-transform": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", @@ -44728,26 +44449,11 @@ "language-tags": "^1.0.5" }, "dependencies": { - "@babel/runtime": { - "version": "7.13.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", - "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, "emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true } } }, @@ -46919,16 +46625,11 @@ "dev": true }, "history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/history/-/history-5.2.0.tgz", + "integrity": "sha512-uPSF6lAJb3nSePJ43hN3eKj1dTWpN9gMod0ZssbFTIsen+WehTmEadgL+kg78xLJFdRfrrC//SavDzmRVdE+Ig==", "requires": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" + "@babel/runtime": "^7.7.6" } }, "hmac-drbg": { @@ -49941,30 +49642,6 @@ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true }, - "mini-create-react-context": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz", - "integrity": "sha512-b0TytUgFSbgFJGzJqXPKCFCBWigAjpjo+Fl7Vf7ZbKRDptszpppKxXH6DRXEABZ/gcEQczeb0iZ7JvL8e8jjCA==", - "requires": { - "@babel/runtime": "^7.5.5", - "tiny-warning": "^1.0.3" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } - } - }, "mini-css-extract-plugin": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.3.1.tgz", @@ -51298,6 +50975,7 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, "requires": { "isarray": "0.0.1" }, @@ -51305,7 +50983,8 @@ "isarray": { "version": "0.0.1", "resolved": "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true } } }, @@ -55656,12 +55335,6 @@ "requires": { "asap": "~2.0.6" } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true } } }, @@ -55972,55 +55645,28 @@ "react-is": "^16.13.1" }, "dependencies": { - "@babel/runtime": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", - "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, "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==" - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" } } }, "react-router": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", - "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.2.1.tgz", + "integrity": "sha512-2fG0udBtxou9lXtK97eJeET2ki5//UWfQSl1rlJ7quwe6jrktK9FCCc8dQb5QY6jAv3jua8bBQRhhDOM/kVRsg==", "requires": { - "@babel/runtime": "^7.1.2", - "history": "^4.9.0", - "hoist-non-react-statics": "^3.1.0", - "loose-envify": "^1.3.1", - "mini-create-react-context": "^0.4.0", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.2", - "react-is": "^16.6.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" + "history": "^5.2.0" } }, "react-router-dom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", - "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.2.1.tgz", + "integrity": "sha512-I6Zax+/TH/cZMDpj3/4Fl2eaNdcvoxxHoH1tYOREsQ22OKDYofGebrNm6CTPUcvLvZm63NL/vzCYdjf9CUhqmA==", "requires": { - "@babel/runtime": "^7.1.2", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.2.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" + "history": "^5.2.0", + "react-router": "6.2.1" } }, "react-shallow-renderer": { @@ -56082,21 +55728,6 @@ "prop-types": "^15.5.8", "react-popper": "^1.3.6", "react-transition-group": "^2.3.1" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.8.tgz", - "integrity": "sha512-CwQljpw6qSayc0fRG1soxHAKs1CnQMOChm4mlQP6My0kf9upVGizj/KhlTTgyUnETmHpcUXjaluNAkteRFuafg==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - } } }, "read-pkg": { @@ -56278,9 +55909,9 @@ } }, "regenerator-runtime": { - "version": "0.12.1", - "resolved": "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", - "integrity": "sha1-+hpxVEdkwDb4xJsToIsllMn4oN4=" + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, "regenerator-transform": { "version": "0.14.5", @@ -56289,23 +55920,6 @@ "dev": true, "requires": { "@babel/runtime": "^7.8.4" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.8.tgz", - "integrity": "sha512-CwQljpw6qSayc0fRG1soxHAKs1CnQMOChm4mlQP6My0kf9upVGizj/KhlTTgyUnETmHpcUXjaluNAkteRFuafg==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - } } }, "regex-not": { @@ -56672,11 +56286,6 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, - "resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" - }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -59698,16 +59307,6 @@ "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", "dev": true }, - "tiny-invariant": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", - "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" - }, - "tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" - }, "tinycolor2": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz", @@ -60660,11 +60259,6 @@ "spdx-expression-parse": "^3.0.0" } }, - "value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" - }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -61689,15 +61283,6 @@ "workbox-window": "^6.1.5" }, "dependencies": { - "@babel/runtime": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.0.tgz", - "integrity": "sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -61710,12 +61295,6 @@ "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", "dev": true }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - }, "source-map": { "version": "0.8.0-beta.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", diff --git a/package.json b/package.json index f1cae125..3d4c6102 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "react-external-link": "^1.2.0", "react-leaflet": "^3.1.0", "react-redux": "^7.2.2", - "react-router-dom": "^5.2.0", + "react-router-dom": "^6.2.1", "react-swipeable": "^6.0.1", "react-tag-autocomplete": "^6.1.0", "reactstrap": "^8.9.0", @@ -86,7 +86,6 @@ "@types/react-dom": "^17.0.1", "@types/react-leaflet": "^2.5.2", "@types/react-redux": "^7.1.16", - "@types/react-router-dom": "^5.1.7", "@types/react-tag-autocomplete": "^6.1.0", "@types/uuid": "^8.3.0", "@wojtekmaj/enzyme-adapter-react-17": "^0.6.5", diff --git a/src/app/App.tsx b/src/app/App.tsx index 95279ee4..499841f5 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,5 +1,5 @@ import { useEffect, FC } from 'react'; -import { Route, RouteChildrenProps, Switch } from 'react-router-dom'; +import { Route, Routes, useLocation } from 'react-router-dom'; import classNames from 'classnames'; import NotFound from '../common/NotFound'; import { ServersMap } from '../servers/data'; @@ -9,7 +9,7 @@ import { AppUpdateBanner } from '../common/AppUpdateBanner'; import { forceUpdate } from '../utils/helpers/sw'; import './App.scss'; -interface AppProps extends RouteChildrenProps { +interface AppProps { fetchServers: () => void; servers: ServersMap; settings: Settings; @@ -26,7 +26,8 @@ const App = ( Settings: FC, ManageServers: FC, ShlinkVersionsContainer: FC, -) => ({ fetchServers, servers, settings, appUpdated, resetAppUpdate, location }: AppProps) => { +) => ({ fetchServers, servers, settings, appUpdated, resetAppUpdate }: AppProps) => { + const location = useLocation(); const isHome = location.pathname === '/'; useEffect(() => { @@ -44,15 +45,15 @@ const App = (
- - - - - - - - - + + } /> + } /> + } /> + } /> + } /> + } /> + } /> +
diff --git a/src/app/services/provideServices.ts b/src/app/services/provideServices.ts index 752c1cc3..50bb8f12 100644 --- a/src/app/services/provideServices.ts +++ b/src/app/services/provideServices.ts @@ -1,9 +1,9 @@ -import Bottle, { Decorator } from 'bottlejs'; +import Bottle from 'bottlejs'; import { appUpdateAvailable, resetAppUpdate } from '../reducers/appUpdates'; import App from '../App'; import { ConnectDecorator } from '../../container/types'; -const provideServices = (bottle: Bottle, connect: ConnectDecorator, withRouter: Decorator) => { +const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { // Components bottle.serviceFactory( 'App', @@ -18,7 +18,6 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator, withRouter: 'ShlinkVersionsContainer', ); bottle.decorator('App', connect([ 'servers', 'settings', 'appUpdated' ], [ 'fetchServers', 'resetAppUpdate' ])); - bottle.decorator('App', withRouter); // Actions bottle.serviceFactory('appUpdateAvailable', () => appUpdateAvailable); diff --git a/src/common/AsideMenu.tsx b/src/common/AsideMenu.tsx index 9c086e27..08ed301d 100644 --- a/src/common/AsideMenu.tsx +++ b/src/common/AsideMenu.tsx @@ -8,9 +8,8 @@ import { } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FC } from 'react'; -import { NavLink, NavLinkProps } from 'react-router-dom'; +import { NavLink, NavLinkProps, useLocation } from 'react-router-dom'; import classNames from 'classnames'; -import { Location } from 'history'; import { DeleteServerButtonProps } from '../servers/DeleteServerButton'; import { isServerWithId, SelectedServer } from '../servers/data'; import { supportsDomainRedirects } from '../utils/helpers/features'; @@ -28,8 +27,7 @@ interface AsideMenuItemProps extends NavLinkProps { const AsideMenuItem: FC = ({ children, to, className, ...rest }) => ( classNames('aside-menu__item', className, { 'aside-menu__item--selected': isActive })} to={to} {...rest} > @@ -42,11 +40,11 @@ const AsideMenu = (DeleteServerButton: FC) => ( ) => { const hasId = isServerWithId(selectedServer); const serverId = hasId ? selectedServer.id : ''; + const { pathname } = useLocation(); const addManageDomainsLink = supportsDomainRedirects(selectedServer); const asideClass = classNames('aside-menu', { 'aside-menu--hidden': !showOnMobile, }); - const shortUrlsIsActive = (_: null, location: Location) => location.pathname.match('/list-short-urls') !== null; const buildPath = (suffix: string) => `/server/${serverId}${suffix}`; return ( @@ -56,7 +54,10 @@ const AsideMenu = (DeleteServerButton: FC) => ( Overview - + List short URLs diff --git a/src/common/Home.tsx b/src/common/Home.tsx index 2ca4f42d..6869c7ab 100644 --- a/src/common/Home.tsx +++ b/src/common/Home.tsx @@ -1,6 +1,6 @@ import { useEffect } from 'react'; import { isEmpty, values } from 'ramda'; -import { Link, RouteChildrenProps } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import { Card, Row } from 'reactstrap'; import { ExternalLink } from 'react-external-link'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -10,11 +10,12 @@ import { ServersMap } from '../servers/data'; import { ShlinkLogo } from './img/ShlinkLogo'; import './Home.scss'; -export interface HomeProps extends RouteChildrenProps { +export interface HomeProps { servers: ServersMap; } -const Home = ({ servers, history }: HomeProps) => { +const Home = ({ servers }: HomeProps) => { + const navigate = useNavigate(); const serversList = values(servers); const hasServers = !isEmpty(serversList); @@ -22,7 +23,7 @@ const Home = ({ servers, history }: HomeProps) => { // Try to redirect to the first server marked as auto-connect const autoConnectServer = serversList.find(({ autoConnect }) => autoConnect); - autoConnectServer && history.push(`/server/${autoConnectServer.id}`); + autoConnectServer && navigate(`/server/${autoConnectServer.id}`); }, []); return ( diff --git a/src/common/MainHeader.tsx b/src/common/MainHeader.tsx index 245a0499..4ce52d68 100644 --- a/src/common/MainHeader.tsx +++ b/src/common/MainHeader.tsx @@ -1,16 +1,16 @@ import { faChevronDown as arrowIcon, faCogs as cogsIcon } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FC, useEffect } from 'react'; -import { Link } from 'react-router-dom'; +import { Link, useLocation } from 'react-router-dom'; import { Collapse, Nav, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap'; import classNames from 'classnames'; -import { RouteComponentProps } from 'react-router'; import { useToggle } from '../utils/helpers/hooks'; import { ShlinkLogo } from './img/ShlinkLogo'; import './MainHeader.scss'; -const MainHeader = (ServersDropdown: FC) => ({ location }: RouteComponentProps) => { +const MainHeader = (ServersDropdown: FC) => () => { const [ isOpen, toggleOpen, , close ] = useToggle(); + const location = useLocation(); const { pathname } = location; useEffect(close, [ location ]); diff --git a/src/common/MenuLayout.tsx b/src/common/MenuLayout.tsx index 2c1c146e..567587b4 100644 --- a/src/common/MenuLayout.tsx +++ b/src/common/MenuLayout.tsx @@ -1,5 +1,5 @@ import { FC, useEffect } from 'react'; -import { Redirect, Route, Switch } from 'react-router-dom'; +import { Navigate, Route, Routes, useLocation } from 'react-router-dom'; import { faBars as burgerIcon } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import classNames from 'classnames'; @@ -24,7 +24,8 @@ const MenuLayout = ( Overview: FC, EditShortUrl: FC, ManageDomains: FC, -) => withSelectedServer(({ location, selectedServer }) => { +) => withSelectedServer(({ selectedServer }) => { + const location = useLocation(); const [ sidebarVisible, toggleSidebar, showSidebar, hideSidebar ] = useToggle(); useEffect(() => hideSidebar(), [ location ]); @@ -48,22 +49,23 @@ const MenuLayout = (
hideSidebar()}>
- - - - - - - - - {addOrphanVisitsRoute && } - {addNonOrphanVisitsRoute && } - - {addManageDomainsRoute && } + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + {addOrphanVisitsRoute && } />} + {addNonOrphanVisitsRoute && } />} + } /> + {addManageDomainsRoute && } />} List short URLs} + path="*" + element={List short URLs} /> - +
diff --git a/src/common/ScrollToTop.tsx b/src/common/ScrollToTop.tsx index 8343b62e..66c23665 100644 --- a/src/common/ScrollToTop.tsx +++ b/src/common/ScrollToTop.tsx @@ -1,7 +1,9 @@ -import { PropsWithChildren, useEffect } from 'react'; -import { RouteComponentProps } from 'react-router'; +import { FC, useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; + +const ScrollToTop = (): FC => ({ children }) => { + const location = useLocation(); -const ScrollToTop = () => ({ location, children }: PropsWithChildren) => { useEffect(() => { scrollTo(0, 0); }, [ location ]); diff --git a/src/common/services/provideServices.ts b/src/common/services/provideServices.ts index ab1f58b1..8c7cc2af 100644 --- a/src/common/services/provideServices.ts +++ b/src/common/services/provideServices.ts @@ -1,5 +1,5 @@ import axios from 'axios'; -import Bottle, { Decorator } from 'bottlejs'; +import Bottle from 'bottlejs'; import ScrollToTop from '../ScrollToTop'; import MainHeader from '../MainHeader'; import Home from '../Home'; @@ -11,7 +11,7 @@ import { ConnectDecorator } from '../../container/types'; import { withoutSelectedServer } from '../../servers/helpers/withoutSelectedServer'; import { ImageDownloader } from './ImageDownloader'; -const provideServices = (bottle: Bottle, connect: ConnectDecorator, withRouter: Decorator) => { +const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { // Services bottle.constant('window', (global as any).window); bottle.constant('console', global.console); @@ -21,14 +21,11 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator, withRouter: // Components bottle.serviceFactory('ScrollToTop', ScrollToTop); - bottle.decorator('ScrollToTop', withRouter); bottle.serviceFactory('MainHeader', MainHeader, 'ServersDropdown'); - bottle.decorator('MainHeader', withRouter); bottle.serviceFactory('Home', () => Home); bottle.decorator('Home', withoutSelectedServer); - bottle.decorator('Home', withRouter); bottle.decorator('Home', connect([ 'servers' ], [ 'resetSelectedServer' ])); bottle.serviceFactory( @@ -48,7 +45,6 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator, withRouter: 'ManageDomains', ); bottle.decorator('MenuLayout', connect([ 'selectedServer' ], [ 'selectServer' ])); - bottle.decorator('MenuLayout', withRouter); bottle.serviceFactory('AsideMenu', AsideMenu, 'DeleteServerButton'); diff --git a/src/container/index.ts b/src/container/index.ts index aedf0ece..8ad16e7b 100644 --- a/src/container/index.ts +++ b/src/container/index.ts @@ -1,5 +1,4 @@ import Bottle, { IContainer } from 'bottlejs'; -import { withRouter } from 'react-router-dom'; import { connect as reduxConnect } from 'react-redux'; import { pick } from 'ramda'; import provideApiServices from '../api/services/provideServices'; @@ -34,11 +33,11 @@ const connect: ConnectDecorator = (propsFromState: string[] | null, actionServic actionServiceNames.reduce(mapActionService, {}), ); -provideAppServices(bottle, connect, withRouter); -provideCommonServices(bottle, connect, withRouter); +provideAppServices(bottle, connect); +provideCommonServices(bottle, connect); provideApiServices(bottle); -provideShortUrlsServices(bottle, connect, withRouter); -provideServersServices(bottle, connect, withRouter); +provideShortUrlsServices(bottle, connect); +provideServersServices(bottle, connect); provideTagsServices(bottle, connect); provideVisitsServices(bottle, connect); provideUtilsServices(bottle); diff --git a/src/mercure/helpers/boundToMercureHub.tsx b/src/mercure/helpers/boundToMercureHub.tsx index 82ee96b7..bc7394b7 100644 --- a/src/mercure/helpers/boundToMercureHub.tsx +++ b/src/mercure/helpers/boundToMercureHub.tsx @@ -1,5 +1,6 @@ import { FC, useEffect } from 'react'; import { pipe } from 'ramda'; +import { useParams } from 'react-router-dom'; import { CreateVisit } from '../../visits/types'; import { MercureInfo } from '../reducers/mercureInfo'; import { bindToMercureTopic } from './index'; @@ -12,17 +13,19 @@ export interface MercureBoundProps { export function boundToMercureHub( WrappedComponent: FC, - getTopicsForProps: (props: T) => string[], + getTopicsForProps: (props: T, routeParams: any) => string[], ) { const pendingUpdates = new Set(); return (props: MercureBoundProps & T) => { const { createNewVisits, loadMercureInfo, mercureInfo } = props; const { interval } = mercureInfo; + const params = useParams(); useEffect(() => { const onMessage = (visit: CreateVisit) => interval ? pendingUpdates.add(visit) : createNewVisits([ visit ]); - const closeEventSource = bindToMercureTopic(mercureInfo, getTopicsForProps(props), onMessage, loadMercureInfo); + const topics = getTopicsForProps(props, params); + const closeEventSource = bindToMercureTopic(mercureInfo, topics, onMessage, loadMercureInfo); if (!interval) { return closeEventSource; diff --git a/src/servers/CreateServer.tsx b/src/servers/CreateServer.tsx index 0412016a..e1d86ce2 100644 --- a/src/servers/CreateServer.tsx +++ b/src/servers/CreateServer.tsx @@ -1,10 +1,10 @@ import { FC, useEffect, useState } from 'react'; import { v4 as uuid } from 'uuid'; -import { RouterProps } from 'react-router'; import { Button } from 'reactstrap'; +import { useNavigate } from 'react-router-dom'; import { Result } from '../utils/Result'; import { NoMenuLayout } from '../common/NoMenuLayout'; -import { StateFlagTimeout, useToggle } from '../utils/helpers/hooks'; +import { StateFlagTimeout, useGoBack, useToggle } from '../utils/helpers/hooks'; import { ServerForm } from './helpers/ServerForm'; import { ImportServersBtnProps } from './helpers/ImportServersBtn'; import { ServerData, ServersMap, ServerWithId } from './data'; @@ -12,7 +12,7 @@ import { DuplicatedServersModal } from './helpers/DuplicatedServersModal'; const SHOW_IMPORT_MSG_TIME = 4000; -interface CreateServerProps extends RouterProps { +interface CreateServerProps { createServer: (server: ServerWithId) => void; servers: ServersMap; } @@ -27,8 +27,10 @@ const ImportResult = ({ type }: { type: 'error' | 'success' }) => ( ); const CreateServer = (ImportServersBtn: FC, useStateFlagTimeout: StateFlagTimeout) => ( - { servers, createServer, history: { push, goBack } }: CreateServerProps, + { servers, createServer }: CreateServerProps, ) => { + const navigate = useNavigate(); + const goBack = useGoBack(); const hasServers = !!Object.keys(servers).length; const [ serversImported, setServersImported ] = useStateFlagTimeout(false, SHOW_IMPORT_MSG_TIME); const [ errorImporting, setErrorImporting ] = useStateFlagTimeout(false, SHOW_IMPORT_MSG_TIME); @@ -42,7 +44,7 @@ const CreateServer = (ImportServersBtn: FC, useStateFlagT const id = uuid(); createServer({ ...serverData, id }); - push(`/server/${id}`); + navigate(`/server/${id}`); }; useEffect(() => { diff --git a/src/servers/DeleteServerModal.tsx b/src/servers/DeleteServerModal.tsx index 491d2f95..499479a8 100644 --- a/src/servers/DeleteServerModal.tsx +++ b/src/servers/DeleteServerModal.tsx @@ -1,6 +1,6 @@ import { FC } from 'react'; import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; -import { RouterProps } from 'react-router'; +import { useNavigate } from 'react-router-dom'; import { ServerWithId } from './data'; export interface DeleteServerModalProps { @@ -10,17 +10,18 @@ export interface DeleteServerModalProps { redirectHome?: boolean; } -interface DeleteServerModalConnectProps extends DeleteServerModalProps, RouterProps { +interface DeleteServerModalConnectProps extends DeleteServerModalProps { deleteServer: (server: ServerWithId) => void; } const DeleteServerModal: FC = ( - { server, toggle, isOpen, deleteServer, history, redirectHome = true }, + { server, toggle, isOpen, deleteServer, redirectHome = true }, ) => { + const navigate = useNavigate(); const closeModal = () => { deleteServer(server); toggle(); - redirectHome && history.push('/'); + redirectHome && navigate('/'); }; return ( diff --git a/src/servers/EditServer.tsx b/src/servers/EditServer.tsx index 514ebcba..201cb6c7 100644 --- a/src/servers/EditServer.tsx +++ b/src/servers/EditServer.tsx @@ -1,6 +1,7 @@ import { FC } from 'react'; import { Button } from 'reactstrap'; import { NoMenuLayout } from '../common/NoMenuLayout'; +import { useGoBack } from '../utils/helpers/hooks'; import { ServerForm } from './helpers/ServerForm'; import { withSelectedServer } from './helpers/withSelectedServer'; import { isServerWithId, ServerData } from './data'; @@ -9,9 +10,9 @@ interface EditServerProps { editServer: (serverId: string, serverData: ServerData) => void; } -export const EditServer = (ServerError: FC) => withSelectedServer(( - { editServer, selectedServer, history: { goBack } }, -) => { +export const EditServer = (ServerError: FC) => withSelectedServer(({ editServer, selectedServer }) => { + const goBack = useGoBack(); + if (!isServerWithId(selectedServer)) { return null; } diff --git a/src/servers/Overview.tsx b/src/servers/Overview.tsx index 0605fc40..a4d95867 100644 --- a/src/servers/Overview.tsx +++ b/src/servers/Overview.tsx @@ -1,6 +1,6 @@ import { FC, useEffect } from 'react'; import { Card, CardBody, CardHeader, Row } from 'reactstrap'; -import { Link, useHistory } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import { ITEMS_IN_OVERVIEW_PAGE, ShortUrlsList as ShortUrlsListState } from '../short-urls/reducers/shortUrlsList'; import { prettify } from '../utils/helpers/numbers'; import { TagsList } from '../tags/reducers/tagsList'; @@ -44,7 +44,7 @@ export const Overview = ( const serverId = getServerId(selectedServer); const linkToOrphanVisits = supportsOrphanVisits(selectedServer); const linkToNonOrphanVisits = supportsNonOrphanVisits(selectedServer); - const history = useHistory(); + const navigate = useNavigate(); useEffect(() => { listShortUrls({ itemsPerPage: ITEMS_IN_OVERVIEW_PAGE, orderBy: { field: 'dateCreated', dir: 'DESC' } }); @@ -103,7 +103,7 @@ export const Overview = ( shortUrlsList={shortUrlsList} selectedServer={selectedServer} className="mb-0" - onTagClick={(tag) => history.push(`/server/${serverId}/list-short-urls/1?tags=${encodeURIComponent(tag)}`)} + onTagClick={(tag) => navigate(`/server/${serverId}/list-short-urls/1?tags=${encodeURIComponent(tag)}`)} /> diff --git a/src/servers/helpers/withSelectedServer.tsx b/src/servers/helpers/withSelectedServer.tsx index 4daa74fc..f3c9d159 100644 --- a/src/servers/helpers/withSelectedServer.tsx +++ b/src/servers/helpers/withSelectedServer.tsx @@ -1,21 +1,22 @@ import { FC, useEffect } from 'react'; -import { RouteComponentProps } from 'react-router'; +import { useParams } from 'react-router-dom'; import Message from '../../utils/Message'; import { isNotFoundServer, SelectedServer } from '../data'; import { NoMenuLayout } from '../../common/NoMenuLayout'; -interface WithSelectedServerProps extends RouteComponentProps<{ serverId: string }> { +interface WithSelectedServerProps { selectServer: (serverId: string) => void; selectedServer: SelectedServer; } export function withSelectedServer(WrappedComponent: FC, ServerError: FC) { return (props: WithSelectedServerProps & T) => { - const { selectServer, selectedServer, match } = props; + const params = useParams<{ serverId: string }>(); + const { selectServer, selectedServer } = props; useEffect(() => { - selectServer(match.params.serverId); - }, [ match.params.serverId ]); + params.serverId && selectServer(params.serverId); + }, [ params.serverId ]); if (!selectedServer) { return ( diff --git a/src/servers/services/provideServices.ts b/src/servers/services/provideServices.ts index 850b55aa..d8d6b2a0 100644 --- a/src/servers/services/provideServices.ts +++ b/src/servers/services/provideServices.ts @@ -1,5 +1,5 @@ import csvjson from 'csvjson'; -import Bottle, { Decorator } from 'bottlejs'; +import Bottle from 'bottlejs'; import CreateServer from '../CreateServer'; import ServersDropdown from '../ServersDropdown'; import DeleteServerModal from '../DeleteServerModal'; @@ -20,7 +20,7 @@ import { ManageServersRowDropdown } from '../ManageServersRowDropdown'; import { ServersImporter } from './ServersImporter'; import ServersExporter from './ServersExporter'; -const provideServices = (bottle: Bottle, connect: ConnectDecorator, withRouter: Decorator) => { +const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { // Components bottle.serviceFactory( 'ManageServers', @@ -48,7 +48,6 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator, withRouter: bottle.decorator('ServersDropdown', connect([ 'servers', 'selectedServer' ])); bottle.serviceFactory('DeleteServerModal', () => DeleteServerModal); - bottle.decorator('DeleteServerModal', withRouter); bottle.decorator('DeleteServerModal', connect(null, [ 'deleteServer' ])); bottle.serviceFactory('DeleteServerButton', DeleteServerButton, 'DeleteServerModal'); diff --git a/src/short-urls/EditShortUrl.tsx b/src/short-urls/EditShortUrl.tsx index 0ecba149..dd4eb88e 100644 --- a/src/short-urls/EditShortUrl.tsx +++ b/src/short-urls/EditShortUrl.tsx @@ -1,9 +1,9 @@ import { FC, useEffect, useMemo } from 'react'; -import { RouteComponentProps } from 'react-router'; import { Button, Card } from 'reactstrap'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faArrowLeft } from '@fortawesome/free-solid-svg-icons'; import { ExternalLink } from 'react-external-link'; +import { useLocation, useParams } from 'react-router-dom'; import { SelectedServer } from '../servers/data'; import { Settings, ShortUrlCreationSettings } from '../settings/reducers/settings'; import { OptionalString } from '../utils/utils'; @@ -11,13 +11,13 @@ import { parseQuery } from '../utils/helpers/query'; import Message from '../utils/Message'; import { Result } from '../utils/Result'; import { ShlinkApiError } from '../api/ShlinkApiError'; -import { useToggle } from '../utils/helpers/hooks'; +import { useGoBack, useToggle } from '../utils/helpers/hooks'; import { ShortUrlFormProps } from './ShortUrlForm'; import { ShortUrlDetail } from './reducers/shortUrlDetail'; import { EditShortUrlData, ShortUrl, ShortUrlData } from './data'; import { ShortUrlEdition } from './reducers/shortUrlEdition'; -interface EditShortUrlConnectProps extends RouteComponentProps<{ shortCode: string }> { +interface EditShortUrlConnectProps { settings: Settings; selectedServer: SelectedServer; shortUrlDetail: ShortUrlDetail; @@ -48,9 +48,6 @@ const getInitialState = (shortUrl?: ShortUrl, settings?: ShortUrlCreationSetting }; export const EditShortUrl = (ShortUrlForm: FC) => ({ - history: { goBack }, - match: { params }, - location: { search }, settings: { shortUrlCreation: shortUrlCreationSettings }, selectedServer, shortUrlDetail, @@ -58,6 +55,9 @@ export const EditShortUrl = (ShortUrlForm: FC) => ({ shortUrlEdition, editShortUrl, }: EditShortUrlConnectProps) => { + const { search } = useLocation(); + const params = useParams<{ shortCode: string }>(); + const goBack = useGoBack(); const { loading, error, errorData, shortUrl } = shortUrlDetail; const { saving, error: savingError, errorData: savingErrorData } = shortUrlEdition; const { domain } = parseQuery<{ domain?: string }>(search); @@ -68,7 +68,7 @@ export const EditShortUrl = (ShortUrlForm: FC) => ({ const [ savingSucceeded,, isSuccessful, isNotSuccessful ] = useToggle(); useEffect(() => { - getShortUrlDetail(params.shortCode, domain); + params.shortCode && getShortUrlDetail(params.shortCode, domain); }, []); if (loading) { diff --git a/src/short-urls/ShortUrlsFilteringBar.tsx b/src/short-urls/ShortUrlsFilteringBar.tsx index 48bc76ad..48ef9d50 100644 --- a/src/short-urls/ShortUrlsFilteringBar.tsx +++ b/src/short-urls/ShortUrlsFilteringBar.tsx @@ -2,7 +2,6 @@ import { faTags as tagsIcon } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { isEmpty, pipe } from 'ramda'; import { parseISO } from 'date-fns'; -import { RouteChildrenProps } from 'react-router-dom'; import SearchField from '../utils/SearchField'; import Tag from '../tags/helpers/Tag'; import { DateRangeSelector } from '../utils/dates/DateRangeSelector'; @@ -12,19 +11,17 @@ import { DateRange } from '../utils/dates/types'; import { supportsAllTagsFiltering } from '../utils/helpers/features'; import { SelectedServer } from '../servers/data'; import { TooltipToggleSwitch } from '../utils/TooltipToggleSwitch'; -import { ShortUrlListRouteParams, useShortUrlsQuery } from './helpers/hooks'; +import { useShortUrlsQuery } from './helpers/hooks'; import './ShortUrlsFilteringBar.scss'; -export type ShortUrlsFilteringProps = RouteChildrenProps & { +interface ShortUrlsFilteringProps { selectedServer: SelectedServer; -}; +} const dateOrNull = (date?: string) => date ? parseISO(date) : null; -const ShortUrlsFilteringBar = (colorGenerator: ColorGenerator) => ( - { selectedServer, ...rest }: ShortUrlsFilteringProps, -) => { - const [{ search, tags, startDate, endDate, tagsMode = 'any' }, toFirstPage ] = useShortUrlsQuery(rest); +const ShortUrlsFilteringBar = (colorGenerator: ColorGenerator) => ({ selectedServer }: ShortUrlsFilteringProps) => { + const [{ search, tags, startDate, endDate, tagsMode = 'any' }, toFirstPage ] = useShortUrlsQuery(); const selectedTags = tags?.split(',') ?? []; const setDates = pipe( ({ startDate, endDate }: DateRange) => ({ diff --git a/src/short-urls/ShortUrlsList.tsx b/src/short-urls/ShortUrlsList.tsx index 10865de9..4e966653 100644 --- a/src/short-urls/ShortUrlsList.tsx +++ b/src/short-urls/ShortUrlsList.tsx @@ -1,7 +1,7 @@ import { pipe } from 'ramda'; import { FC, useEffect, useMemo, useState } from 'react'; -import { RouteComponentProps } from 'react-router'; import { Card } from 'reactstrap'; +import { useLocation, useParams } from 'react-router-dom'; import { OrderingDropdown } from '../utils/OrderingDropdown'; import { determineOrderDir, OrderDir } from '../utils/helpers/ordering'; import { getServerId, SelectedServer } from '../servers/data'; @@ -13,10 +13,10 @@ import { DEFAULT_SHORT_URLS_ORDERING, Settings } from '../settings/reducers/sett import { ShortUrlsList as ShortUrlsListState } from './reducers/shortUrlsList'; import { ShortUrlsTableProps } from './ShortUrlsTable'; import Paginator from './Paginator'; -import { ShortUrlListRouteParams, useShortUrlsQuery } from './helpers/hooks'; +import { useShortUrlsQuery } from './helpers/hooks'; import { ShortUrlsOrderableFields, SHORT_URLS_ORDERABLE_FIELDS } from './data'; -interface ShortUrlsListProps extends RouteComponentProps { +interface ShortUrlsListProps { selectedServer: SelectedServer; shortUrlsList: ShortUrlsListState; listShortUrls: (params: ShlinkShortUrlsListParams) => void; @@ -25,18 +25,14 @@ interface ShortUrlsListProps extends RouteComponentProps, ShortUrlsFilteringBar: FC) => boundToMercureHub(({ listShortUrls, - match, - location, - history, shortUrlsList, selectedServer, settings, }: ShortUrlsListProps) => { const serverId = getServerId(selectedServer); - const [ - { tags, search, startDate, endDate, orderBy, tagsMode }, - toFirstPage, - ] = useShortUrlsQuery({ history, match, location }); + const { page } = useParams(); + const location = useLocation(); + const [{ tags, search, startDate, endDate, orderBy, tagsMode }, toFirstPage ] = useShortUrlsQuery(); const [ actualOrderBy, setActualOrderBy ] = useState( // This separated state handling is needed to be able to fall back to settings value, but only once when loaded orderBy ?? settings.shortUrlsList?.defaultOrdering ?? DEFAULT_SHORT_URLS_ORDERING, @@ -58,7 +54,7 @@ const ShortUrlsList = (ShortUrlsTable: FC, ShortUrlsFilteri useEffect(() => { listShortUrls({ - page: match.params.page, + page, searchTerm: search, tags: selectedTags, startDate, @@ -66,7 +62,7 @@ const ShortUrlsList = (ShortUrlsTable: FC, ShortUrlsFilteri orderBy: actualOrderBy, tagsMode, }); - }, [ match.params.page, search, selectedTags, startDate, endDate, actualOrderBy, tagsMode ]); + }, [ page, search, selectedTags, startDate, endDate, actualOrderBy, tagsMode ]); return ( <> diff --git a/src/short-urls/helpers/hooks.ts b/src/short-urls/helpers/hooks.ts index c046e202..9e55e026 100644 --- a/src/short-urls/helpers/hooks.ts +++ b/src/short-urls/helpers/hooks.ts @@ -1,4 +1,4 @@ -import { RouteChildrenProps } from 'react-router-dom'; +import { useParams, useLocation, useNavigate } from 'react-router-dom'; import { useMemo } from 'react'; import { isEmpty, pipe } from 'ramda'; import { parseQuery, stringifyQuery } from '../../utils/helpers/query'; @@ -6,7 +6,6 @@ import { ShortUrlsOrder, ShortUrlsOrderableFields } from '../data'; import { orderToString, stringToOrder } from '../../utils/helpers/ordering'; import { TagsFilteringMode } from '../../api/types'; -type ServerIdRouteProps = RouteChildrenProps<{ serverId: string }>; type ToFirstPage = (extra: Partial) => void; export interface ShortUrlListRouteParams { @@ -30,9 +29,11 @@ interface ShortUrlsFiltering extends ShortUrlsQueryCommon { orderBy?: ShortUrlsOrder; } -export const useShortUrlsQuery = ( - { history, location, match }: ServerIdRouteProps, -): [ShortUrlsFiltering, ToFirstPage] => { +export const useShortUrlsQuery = (): [ShortUrlsFiltering, ToFirstPage] => { + const navigate = useNavigate(); + const location = useLocation(); + const params = useParams<{ serverId: string }>(); + const query = useMemo( pipe( () => parseQuery(location.search), @@ -49,7 +50,7 @@ export const useShortUrlsQuery = ( const evolvedQuery = stringifyQuery(normalizedQuery); const queryString = isEmpty(evolvedQuery) ? '' : `?${evolvedQuery}`; - history.push(`/server/${match?.params.serverId}/list-short-urls/1${queryString}`); + navigate(`/server/${params.serverId}/list-short-urls/1${queryString}`); }; return [ query, toFirstPageWithExtra ]; diff --git a/src/short-urls/services/provideServices.ts b/src/short-urls/services/provideServices.ts index 71b42a7b..82e40f4e 100644 --- a/src/short-urls/services/provideServices.ts +++ b/src/short-urls/services/provideServices.ts @@ -1,4 +1,4 @@ -import Bottle, { Decorator } from 'bottlejs'; +import Bottle from 'bottlejs'; import ShortUrlsFilteringBar from '../ShortUrlsFilteringBar'; import ShortUrlsList from '../ShortUrlsList'; import ShortUrlsRow from '../helpers/ShortUrlsRow'; @@ -17,7 +17,7 @@ import { ShortUrlForm } from '../ShortUrlForm'; import { EditShortUrl } from '../EditShortUrl'; import { getShortUrlDetail } from '../reducers/shortUrlDetail'; -const provideServices = (bottle: Bottle, connect: ConnectDecorator, withRouter: Decorator) => { +const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { // Components bottle.serviceFactory('ShortUrlsList', ShortUrlsList, 'ShortUrlsTable', 'ShortUrlsFilteringBar'); bottle.decorator('ShortUrlsList', connect( @@ -49,10 +49,8 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator, withRouter: bottle.serviceFactory('QrCodeModal', QrCodeModal, 'ImageDownloader', 'ForServerVersion'); bottle.decorator('QrCodeModal', connect([ 'selectedServer' ])); - // Services bottle.serviceFactory('ShortUrlsFilteringBar', ShortUrlsFilteringBar, 'ColorGenerator'); bottle.decorator('ShortUrlsFilteringBar', connect([ 'selectedServer' ])); - bottle.decorator('ShortUrlsFilteringBar', withRouter); // Actions bottle.serviceFactory('listShortUrls', listShortUrls, 'buildShlinkApiClient'); diff --git a/src/tags/TagsTable.tsx b/src/tags/TagsTable.tsx index 94876b65..483a2440 100644 --- a/src/tags/TagsTable.tsx +++ b/src/tags/TagsTable.tsx @@ -1,6 +1,6 @@ import { FC, useEffect, useRef } from 'react'; import { splitEvery } from 'ramda'; -import { RouteChildrenProps } from 'react-router'; +import { useLocation } from 'react-router-dom'; import { SimpleCard } from '../utils/SimpleCard'; import SimplePaginator from '../common/SimplePaginator'; import { useQueryState } from '../utils/helpers/hooks'; @@ -18,10 +18,11 @@ export interface TagsTableProps extends TagsListChildrenProps { const TAGS_PER_PAGE = 20; // TODO Allow customizing this value in settings export const TagsTable = (TagsTableRow: FC) => ( - { sortedTags, selectedServer, location, orderByColumn, currentOrder }: TagsTableProps & RouteChildrenProps, + { sortedTags, selectedServer, orderByColumn, currentOrder }: TagsTableProps, ) => { const isFirstLoad = useRef(true); - const { page: pageFromQuery = 1 } = parseQuery<{ page?: number | string }>(location.search); + const { search } = useLocation(); + const { page: pageFromQuery = 1 } = parseQuery<{ page?: number | string }>(search); const [ page, setPage ] = useQueryState('page', Number(pageFromQuery)); const pages = splitEvery(TAGS_PER_PAGE, sortedTags); const showPaginator = pages.length > 1; diff --git a/src/tags/services/provideServices.ts b/src/tags/services/provideServices.ts index 0068d9d6..99009367 100644 --- a/src/tags/services/provideServices.ts +++ b/src/tags/services/provideServices.ts @@ -1,5 +1,4 @@ import Bottle, { IContainer } from 'bottlejs'; -import { withRouter } from 'react-router-dom'; import TagsSelector from '../helpers/TagsSelector'; import TagCard from '../TagCard'; import DeleteTagConfirmModal from '../helpers/DeleteTagConfirmModal'; @@ -30,7 +29,6 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { bottle.serviceFactory('TagsTableRow', TagsTableRow, 'DeleteTagConfirmModal', 'EditTagModal', 'ColorGenerator'); bottle.serviceFactory('TagsTable', TagsTable, 'TagsTableRow'); - bottle.decorator('TagsTable', withRouter); bottle.serviceFactory('TagsList', TagsList, 'TagsCards', 'TagsTable'); bottle.decorator('TagsList', connect( diff --git a/src/utils/helpers/hooks.ts b/src/utils/helpers/hooks.ts index 7d908852..63b566bb 100644 --- a/src/utils/helpers/hooks.ts +++ b/src/utils/helpers/hooks.ts @@ -1,5 +1,6 @@ import { useState, useRef, EffectCallback, DependencyList, useEffect } from 'react'; import { useSwipeable as useReactSwipeable } from 'react-swipeable'; +import { useNavigate } from 'react-router-dom'; import { parseQuery, stringifyQuery } from './query'; const DEFAULT_DELAY = 2000; @@ -75,3 +76,9 @@ export const useEffectExceptFirstTime = (callback: EffectCallback, deps: Depende isFirstLoad.current = false; }, deps); }; + +export const useGoBack = () => { + const navigate = useNavigate(); + + return () => navigate(-1); +}; diff --git a/src/visits/NonOrphanVisits.tsx b/src/visits/NonOrphanVisits.tsx index 947111ff..e6dc6a26 100644 --- a/src/visits/NonOrphanVisits.tsx +++ b/src/visits/NonOrphanVisits.tsx @@ -1,7 +1,7 @@ -import { RouteComponentProps } from 'react-router'; import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub'; import { ShlinkVisitsParams } from '../api/types'; import { Topics } from '../mercure/helpers/Topics'; +import { useGoBack } from '../utils/helpers/hooks'; import VisitsStats from './VisitsStats'; import { NormalizedVisit, VisitsInfo, VisitsParams } from './types'; import { VisitsExporter } from './services/VisitsExporter'; @@ -9,21 +9,20 @@ import { CommonVisitsProps } from './types/CommonVisitsProps'; import { toApiParams } from './types/helpers'; import { NonOrphanVisitsHeader } from './NonOrphanVisitsHeader'; -export interface NonOrphanVisitsProps extends CommonVisitsProps, RouteComponentProps { +export interface NonOrphanVisitsProps extends CommonVisitsProps { getNonOrphanVisits: (params?: ShlinkVisitsParams, doIntervalFallback?: boolean) => void; nonOrphanVisits: VisitsInfo; cancelGetNonOrphanVisits: () => void; } export const NonOrphanVisits = ({ exportVisits }: VisitsExporter) => boundToMercureHub(({ - history: { goBack }, - match: { url }, getNonOrphanVisits, nonOrphanVisits, cancelGetNonOrphanVisits, settings, selectedServer, }: NonOrphanVisitsProps) => { + const goBack = useGoBack(); const exportCsv = (visits: NormalizedVisit[]) => exportVisits('non_orphan_visits.csv', visits); const loadVisits = (params: VisitsParams, doIntervalFallback?: boolean) => getNonOrphanVisits(toApiParams(params), doIntervalFallback); @@ -33,7 +32,6 @@ export const NonOrphanVisits = ({ exportVisits }: VisitsExporter) => boundToMerc getVisits={loadVisits} cancelGetVisits={cancelGetNonOrphanVisits} visitsInfo={nonOrphanVisits} - baseUrl={url} settings={settings} exportCsv={exportCsv} selectedServer={selectedServer} diff --git a/src/visits/OrphanVisits.tsx b/src/visits/OrphanVisits.tsx index af4dc7a4..a659f5ea 100644 --- a/src/visits/OrphanVisits.tsx +++ b/src/visits/OrphanVisits.tsx @@ -1,7 +1,7 @@ -import { RouteComponentProps } from 'react-router'; import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub'; import { ShlinkVisitsParams } from '../api/types'; import { Topics } from '../mercure/helpers/Topics'; +import { useGoBack } from '../utils/helpers/hooks'; import VisitsStats from './VisitsStats'; import { OrphanVisitsHeader } from './OrphanVisitsHeader'; import { NormalizedVisit, OrphanVisitType, VisitsInfo, VisitsParams } from './types'; @@ -9,7 +9,7 @@ import { VisitsExporter } from './services/VisitsExporter'; import { CommonVisitsProps } from './types/CommonVisitsProps'; import { toApiParams } from './types/helpers'; -export interface OrphanVisitsProps extends CommonVisitsProps, RouteComponentProps { +export interface OrphanVisitsProps extends CommonVisitsProps { getOrphanVisits: ( params?: ShlinkVisitsParams, orphanVisitsType?: OrphanVisitType, @@ -20,14 +20,13 @@ export interface OrphanVisitsProps extends CommonVisitsProps, RouteComponentProp } export const OrphanVisits = ({ exportVisits }: VisitsExporter) => boundToMercureHub(({ - history: { goBack }, - match: { url }, getOrphanVisits, orphanVisits, cancelGetOrphanVisits, settings, selectedServer, }: OrphanVisitsProps) => { + const goBack = useGoBack(); const exportCsv = (visits: NormalizedVisit[]) => exportVisits('orphan_visits.csv', visits); const loadVisits = (params: VisitsParams, doIntervalFallback?: boolean) => getOrphanVisits(toApiParams(params), params.filter?.orphanVisitsType, doIntervalFallback); @@ -37,7 +36,6 @@ export const OrphanVisits = ({ exportVisits }: VisitsExporter) => boundToMercure getVisits={loadVisits} cancelGetVisits={cancelGetOrphanVisits} visitsInfo={orphanVisits} - baseUrl={url} settings={settings} exportCsv={exportCsv} selectedServer={selectedServer} diff --git a/src/visits/ShortUrlVisits.tsx b/src/visits/ShortUrlVisits.tsx index fcb7a0c1..5956006f 100644 --- a/src/visits/ShortUrlVisits.tsx +++ b/src/visits/ShortUrlVisits.tsx @@ -1,10 +1,11 @@ import { useEffect } from 'react'; -import { RouteComponentProps } from 'react-router'; +import { useLocation, useParams } from 'react-router-dom'; import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub'; import { ShlinkVisitsParams } from '../api/types'; import { parseQuery } from '../utils/helpers/query'; import { Topics } from '../mercure/helpers/Topics'; import { ShortUrlDetail } from '../short-urls/reducers/shortUrlDetail'; +import { useGoBack } from '../utils/helpers/hooks'; import { ShortUrlVisits as ShortUrlVisitsState } from './reducers/shortUrlVisits'; import ShortUrlVisitsHeader from './ShortUrlVisitsHeader'; import VisitsStats from './VisitsStats'; @@ -13,7 +14,7 @@ import { NormalizedVisit, VisitsParams } from './types'; import { CommonVisitsProps } from './types/CommonVisitsProps'; import { toApiParams } from './types/helpers'; -export interface ShortUrlVisitsProps extends CommonVisitsProps, RouteComponentProps<{ shortCode: string }> { +export interface ShortUrlVisitsProps extends CommonVisitsProps { getShortUrlVisits: (shortCode: string, query?: ShlinkVisitsParams, doIntervalFallback?: boolean) => void; shortUrlVisits: ShortUrlVisitsState; getShortUrlDetail: Function; @@ -22,9 +23,6 @@ export interface ShortUrlVisitsProps extends CommonVisitsProps, RouteComponentPr } const ShortUrlVisits = ({ exportVisits }: VisitsExporter) => boundToMercureHub(({ - history: { goBack }, - match: { params, url }, - location: { search }, shortUrlVisits, shortUrlDetail, getShortUrlVisits, @@ -33,7 +31,9 @@ const ShortUrlVisits = ({ exportVisits }: VisitsExporter) => boundToMercureHub(( settings, selectedServer, }: ShortUrlVisitsProps) => { - const { shortCode } = params; + const { shortCode = '' } = useParams<{ shortCode: string }>(); + const { search } = useLocation(); + const goBack = useGoBack(); const { domain } = parseQuery<{ domain?: string }>(search); const loadVisits = (params: VisitsParams, doIntervalFallback?: boolean) => getShortUrlVisits(shortCode, { ...toApiParams(params), domain }, doIntervalFallback); @@ -51,7 +51,6 @@ const ShortUrlVisits = ({ exportVisits }: VisitsExporter) => boundToMercureHub(( getVisits={loadVisits} cancelGetVisits={cancelGetShortUrlVisits} visitsInfo={shortUrlVisits} - baseUrl={url} domain={domain} settings={settings} exportCsv={exportCsv} @@ -60,6 +59,6 @@ const ShortUrlVisits = ({ exportVisits }: VisitsExporter) => boundToMercureHub(( ); -}, ({ match }) => [ Topics.shortUrlVisits(match.params.shortCode) ]); +}, (_, params) => [ Topics.shortUrlVisits(params.shortCode) ]); export default ShortUrlVisits; diff --git a/src/visits/TagVisits.tsx b/src/visits/TagVisits.tsx index 702bd837..7305aaaf 100644 --- a/src/visits/TagVisits.tsx +++ b/src/visits/TagVisits.tsx @@ -1,8 +1,9 @@ -import { RouteComponentProps } from 'react-router'; +import { useParams } from 'react-router-dom'; import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub'; import ColorGenerator from '../utils/services/ColorGenerator'; import { ShlinkVisitsParams } from '../api/types'; import { Topics } from '../mercure/helpers/Topics'; +import { useGoBack } from '../utils/helpers/hooks'; import { TagVisits as TagVisitsState } from './reducers/tagVisits'; import TagVisitsHeader from './TagVisitsHeader'; import VisitsStats from './VisitsStats'; @@ -11,22 +12,21 @@ import { NormalizedVisit } from './types'; import { CommonVisitsProps } from './types/CommonVisitsProps'; import { toApiParams } from './types/helpers'; -export interface TagVisitsProps extends CommonVisitsProps, RouteComponentProps<{ tag: string }> { +export interface TagVisitsProps extends CommonVisitsProps { getTagVisits: (tag: string, query?: ShlinkVisitsParams, doIntervalFallback?: boolean) => void; tagVisits: TagVisitsState; cancelGetTagVisits: () => void; } const TagVisits = (colorGenerator: ColorGenerator, { exportVisits }: VisitsExporter) => boundToMercureHub(({ - history: { goBack }, - match: { params, url }, getTagVisits, tagVisits, cancelGetTagVisits, settings, selectedServer, }: TagVisitsProps) => { - const { tag } = params; + const goBack = useGoBack(); + const { tag = '' } = useParams(); const loadVisits = (params: ShlinkVisitsParams, doIntervalFallback?: boolean) => getTagVisits(tag, toApiParams(params), doIntervalFallback); const exportCsv = (visits: NormalizedVisit[]) => exportVisits(`tag_${tag}_visits.csv`, visits); @@ -36,7 +36,6 @@ const TagVisits = (colorGenerator: ColorGenerator, { exportVisits }: VisitsExpor getVisits={loadVisits} cancelGetVisits={cancelGetTagVisits} visitsInfo={tagVisits} - baseUrl={url} settings={settings} exportCsv={exportCsv} selectedServer={selectedServer} diff --git a/src/visits/VisitsStats.tsx b/src/visits/VisitsStats.tsx index 8818ada7..ff52b15c 100644 --- a/src/visits/VisitsStats.tsx +++ b/src/visits/VisitsStats.tsx @@ -4,7 +4,7 @@ import { Button, Card, Nav, NavLink, Progress, Row } from 'reactstrap'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCalendarAlt, faMapMarkedAlt, faList, faChartPie, faFileDownload } from '@fortawesome/free-solid-svg-icons'; import { IconDefinition } from '@fortawesome/fontawesome-common-types'; -import { Route, Switch, NavLink as RouterNavLink, Redirect } from 'react-router-dom'; +import { Route, Routes, NavLink as RouterNavLink, Navigate } from 'react-router-dom'; import { Location } from 'history'; import classNames from 'classnames'; import { DateRangeSelector } from '../utils/dates/DateRangeSelector'; @@ -33,7 +33,6 @@ export interface VisitsStatsProps { settings: Settings; selectedServer: SelectedServer; cancelGetVisits: () => void; - baseUrl: string; domain?: string; exportCsv: (visits: NormalizedVisit[]) => void; isOrphanVisits?: boolean; @@ -48,10 +47,10 @@ interface VisitsNavLinkProps { type Section = 'byTime' | 'byContext' | 'byLocation' | 'list'; const sections: Record = { - byTime: { title: 'By time', subPath: '', icon: faCalendarAlt }, - byContext: { title: 'By context', subPath: '/by-context', icon: faChartPie }, - byLocation: { title: 'By location', subPath: '/by-location', icon: faMapMarkedAlt }, - list: { title: 'List', subPath: '/list', icon: faList }, + byTime: { title: 'By time', subPath: 'by-time', icon: faCalendarAlt }, + byContext: { title: 'By context', subPath: 'by-context', icon: faChartPie }, + byLocation: { title: 'By location', subPath: 'by-location', icon: faMapMarkedAlt }, + list: { title: 'List', subPath: 'list', icon: faList }, }; let selectedBar: string | undefined; @@ -74,7 +73,6 @@ const VisitsStats: FC = ({ visitsInfo, getVisits, cancelGetVisits, - baseUrl, domain, settings, exportCsv, @@ -95,7 +93,7 @@ const VisitsStats: FC = ({ const buildSectionUrl = (subPath?: string) => { const query = domain ? `?domain=${domain}` : ''; - return !subPath ? `${baseUrl}${query}` : `${baseUrl}${subPath}${query}`; + return !subPath ? `${query}` : `${subPath}${query}`; }; const normalizedVisits = useMemo(() => normalizeVisits(visits), [ visits ]); const { os, browsers, referrers, countries, cities, citiesForMap, visitedUrls } = useMemo( @@ -166,104 +164,120 @@ const VisitsStats: FC = ({ - - -
- -
-
- - -
- -
-
- -
-
- -
- {isOrphanVisits && ( -
- + +
)} -
+ /> - -
- -
-
- - mapLocations.length > 0 && - - } - sortingItems={{ - name: 'City name', - amount: 'Visits amount', - }} - onClick={highlightVisitsForProp('city')} - /> -
-
+ +
+ +
+
+ +
+
+ +
+ {isOrphanVisits && ( +
+ +
+ )} + + )} + /> - -
- -
-
+ +
+ +
+
+ + mapLocations.length > 0 && + + } + sortingItems={{ + name: 'City name', + amount: 'Visits amount', + }} + onClick={highlightVisitsForProp('city')} + /> +
+ + )} + /> - -
+ + +
+ )} + /> + + } /> + ); diff --git a/test/app/App.test.tsx b/test/app/App.test.tsx index a161a44e..4802a983 100644 --- a/test/app/App.test.tsx +++ b/test/app/App.test.tsx @@ -1,12 +1,15 @@ import { shallow, ShallowWrapper } from 'enzyme'; -import { Route } from 'react-router-dom'; +import { Route, useLocation } from 'react-router-dom'; import { Mock } from 'ts-mockery'; -import { match } from 'react-router'; -import { History, Location } from 'history'; import { Settings } from '../../src/settings/reducers/settings'; import appFactory from '../../src/app/App'; import { AppUpdateBanner } from '../../src/common/AppUpdateBanner'; +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLocation: jest.fn().mockReturnValue({}), +})); + describe('', () => { let wrapper: ShallowWrapper; const MainHeader = () => null; @@ -21,7 +24,7 @@ describe('', () => { () => null, ShlinkVersions, ); - const createWrapper = (pathname = '') => { + const createWrapper = () => { wrapper = shallow( {}} @@ -29,16 +32,14 @@ describe('', () => { settings={Mock.all()} appUpdated={false} resetAppUpdate={() => {}} - match={Mock.all()} - history={Mock.all()} - location={Mock.of({ pathname })} />, ); return wrapper; }; - afterEach(() => wrapper.unmount()); + afterEach(jest.clearAllMocks); + afterEach(() => wrapper?.unmount()); it('renders children components', () => { const wrapper = createWrapper(); @@ -52,12 +53,12 @@ describe('', () => { const wrapper = createWrapper(); const routes = wrapper.find(Route); const expectedPaths = [ - '/', + undefined, '/settings', '/manage-servers', '/server/create', '/server/:serverId/edit', - '/server/:serverId', + '/server/:serverId/*', ]; expect.assertions(expectedPaths.length + 1); @@ -72,7 +73,9 @@ describe('', () => { [ '/bar', 'shlink-wrapper' ], [ '/', 'shlink-wrapper d-flex d-md-block align-items-center' ], ])('renders expected classes on shlink-wrapper based on current pathname', (pathname, expectedClasses) => { - const wrapper = createWrapper(pathname); + (useLocation as any).mockReturnValue({ pathname }); // eslint-disable-line @typescript-eslint/no-unsafe-call + + const wrapper = createWrapper(); const shlinkWrapper = wrapper.find('.shlink-wrapper'); expect(shlinkWrapper.prop('className')).toEqual(expectedClasses); diff --git a/test/common/AsideMenu.test.tsx b/test/common/AsideMenu.test.tsx index 22ef6a26..01ad26b1 100644 --- a/test/common/AsideMenu.test.tsx +++ b/test/common/AsideMenu.test.tsx @@ -3,6 +3,11 @@ import { Mock } from 'ts-mockery'; import asideMenuCreator from '../../src/common/AsideMenu'; import { ReachableServer } from '../../src/servers/data'; +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLocation: jest.fn().mockReturnValue({ pathname: '' }), +})); + describe('', () => { let wrapped: ShallowWrapper; const DeleteServerButton = () => null; diff --git a/test/common/Home.test.tsx b/test/common/Home.test.tsx index b24db7d1..96f17a3e 100644 --- a/test/common/Home.test.tsx +++ b/test/common/Home.test.tsx @@ -1,19 +1,18 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { Mock } from 'ts-mockery'; -import { RouteChildrenProps } from 'react-router-dom'; import Home, { HomeProps } from '../../src/common/Home'; import { ServerWithId } from '../../src/servers/data'; import { ShlinkLogo } from '../../src/common/img/ShlinkLogo'; +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: jest.fn().mockReturnValue(jest.fn()), +})); + describe('', () => { let wrapped: ShallowWrapper; - const defaultProps = { - ...Mock.all(), - resetSelectedServer: jest.fn(), - servers: {}, - }; const createComponent = (props: Partial = {}) => { - const actualProps = { ...defaultProps, ...props }; + const actualProps = { resetSelectedServer: jest.fn(), servers: {}, ...props }; wrapped = shallow(); diff --git a/test/common/MainHeader.test.tsx b/test/common/MainHeader.test.tsx index 81ff9d2e..b1fa282e 100644 --- a/test/common/MainHeader.test.tsx +++ b/test/common/MainHeader.test.tsx @@ -1,24 +1,28 @@ import { shallow, ShallowWrapper } from 'enzyme'; -import { Mock } from 'ts-mockery'; -import { match } from 'react-router'; -import { History, Location } from 'history'; +import { useLocation } from 'react-router-dom'; import { Collapse, NavbarToggler, NavLink } from 'reactstrap'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import createMainHeader from '../../src/common/MainHeader'; +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLocation: jest.fn().mockReturnValue({}), +})); + describe('', () => { const ServersDropdown = () => null; const MainHeader = createMainHeader(ServersDropdown); let wrapper: ShallowWrapper; const createWrapper = (pathname = '') => { - const location = Mock.of({ pathname }); + (useLocation as any).mockReturnValue({ pathname }); - wrapper = shallow(()} location={location} match={Mock.all()} />); + wrapper = shallow(); return wrapper; }; + afterEach(jest.clearAllMocks); afterEach(() => wrapper?.unmount()); it('renders ServersDropdown', () => { diff --git a/test/common/MenuLayout.test.tsx b/test/common/MenuLayout.test.tsx index ba5ad007..98306335 100644 --- a/test/common/MenuLayout.test.tsx +++ b/test/common/MenuLayout.test.tsx @@ -1,34 +1,31 @@ import { shallow, ShallowWrapper } from 'enzyme'; -import { History, Location } from 'history'; -import { match } from 'react-router'; // eslint-disable-line @typescript-eslint/no-unused-vars -import { Route } from 'react-router-dom'; +import { Route, useParams } from 'react-router-dom'; import { Mock } from 'ts-mockery'; import createMenuLayout from '../../src/common/MenuLayout'; import { NonReachableServer, NotFoundServer, ReachableServer, SelectedServer } from '../../src/servers/data'; import { NoMenuLayout } from '../../src/common/NoMenuLayout'; import { SemVer } from '../../src/utils/helpers/version'; +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: jest.fn().mockReturnValue({}), + useLocation: jest.fn().mockReturnValue({}), +})); + describe('', () => { const ServerError = jest.fn(); const C = jest.fn(); const MenuLayout = createMenuLayout(C, C, C, C, C, C, C, C, ServerError, C, C, C); let wrapper: ShallowWrapper; const createWrapper = (selectedServer: SelectedServer) => { - wrapper = shallow( - ()} - location={Mock.all()} - match={Mock.of>({ - params: { serverId: 'abc123' }, - })} - />, - ); + (useParams as any).mockReturnValue({ serverId: 'abc123' }); + + wrapper = shallow(); return wrapper; }; + afterEach(jest.clearAllMocks); afterEach(() => wrapper?.unmount()); it.each([ @@ -49,17 +46,18 @@ describe('', () => { }); it.each([ - [ '2.5.0' as SemVer, 8 ], - [ '2.6.0' as SemVer, 9 ], - [ '2.7.0' as SemVer, 9 ], - [ '2.8.0' as SemVer, 10 ], - [ '2.10.0' as SemVer, 10 ], - [ '3.0.0' as SemVer, 11 ], + [ '2.5.0' as SemVer, 9 ], + [ '2.6.0' as SemVer, 10 ], + [ '2.7.0' as SemVer, 10 ], + [ '2.8.0' as SemVer, 11 ], + [ '2.10.0' as SemVer, 11 ], + [ '3.0.0' as SemVer, 12 ], ])('has expected amount of routes based on selected server\'s version', (version, expectedAmountOfRoutes) => { const selectedServer = Mock.of({ version }); const wrapper = createWrapper(selectedServer).dive(); const routes = wrapper.find(Route); expect(routes).toHaveLength(expectedAmountOfRoutes); + expect(routes.findWhere((element) => element.prop('index'))).toHaveLength(1); }); }); diff --git a/test/common/ScrollToTop.test.tsx b/test/common/ScrollToTop.test.tsx index cd3ce860..2720f0d3 100644 --- a/test/common/ScrollToTop.test.tsx +++ b/test/common/ScrollToTop.test.tsx @@ -1,15 +1,18 @@ import { shallow, ShallowWrapper } from 'enzyme'; -import { Mock } from 'ts-mockery'; -import { RouteComponentProps } from 'react-router'; import createScrollToTop from '../../src/common/ScrollToTop'; +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLocation: jest.fn().mockReturnValue({}), +})); + describe('', () => { let wrapper: ShallowWrapper; beforeEach(() => { const ScrollToTop = createScrollToTop(); - wrapper = shallow(()}>Foobar); + wrapper = shallow(Foobar); }); afterEach(() => wrapper.unmount()); diff --git a/test/servers/CreateServer.test.tsx b/test/servers/CreateServer.test.tsx index b55944ed..06fde51c 100644 --- a/test/servers/CreateServer.test.tsx +++ b/test/servers/CreateServer.test.tsx @@ -1,27 +1,29 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { Mock } from 'ts-mockery'; -import { History } from 'history'; +import { useNavigate } from 'react-router-dom'; import createServerConstruct from '../../src/servers/CreateServer'; import { ServerForm } from '../../src/servers/helpers/ServerForm'; import { ServerWithId } from '../../src/servers/data'; import { DuplicatedServersModal } from '../../src/servers/helpers/DuplicatedServersModal'; +jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useNavigate: jest.fn() })); + describe('', () => { let wrapper: ShallowWrapper; const ImportServersBtn = () => null; const createServerMock = jest.fn(); - const push = jest.fn(); - const goBack = jest.fn(); - const historyMock = Mock.of({ push, goBack }); + const navigate = jest.fn(); const servers = { foo: Mock.all() }; const createWrapper = (serversImported = false, importFailed = false) => { + (useNavigate as any).mockReturnValue(navigate); + const useStateFlagTimeout = jest.fn() .mockReturnValueOnce([ serversImported, () => '' ]) .mockReturnValueOnce([ importFailed, () => '' ]) .mockReturnValue([]); const CreateServer = createServerConstruct(ImportServersBtn, useStateFlagTimeout); - wrapper = shallow(); + wrapper = shallow(); return wrapper; }; @@ -68,7 +70,7 @@ describe('', () => { wrapper.find(DuplicatedServersModal).simulate('save'); expect(createServerMock).toHaveBeenCalledTimes(1); - expect(push).toHaveBeenCalledTimes(1); + expect(navigate).toHaveBeenCalledTimes(1); }); it('goes back on modal discard', () => { @@ -76,6 +78,6 @@ describe('', () => { wrapper.find(DuplicatedServersModal).simulate('discard'); - expect(goBack).toHaveBeenCalledTimes(1); + expect(navigate).toHaveBeenCalledWith(-1); }); }); diff --git a/test/servers/DeleteServerModal.test.tsx b/test/servers/DeleteServerModal.test.tsx index 14eed177..151832b2 100644 --- a/test/servers/DeleteServerModal.test.tsx +++ b/test/servers/DeleteServerModal.test.tsx @@ -1,25 +1,28 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; -import { History } from 'history'; import { Mock } from 'ts-mockery'; +import { useNavigate } from 'react-router-dom'; import DeleteServerModal from '../../src/servers/DeleteServerModal'; import { ServerWithId } from '../../src/servers/data'; +jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useNavigate: jest.fn() })); + describe('', () => { let wrapper: ShallowWrapper; const deleteServerMock = jest.fn(); - const push = jest.fn(); + const navigate = jest.fn(); const toggleMock = jest.fn(); const serverName = 'the_server_name'; beforeEach(() => { + (useNavigate as any).mockReturnValue(navigate); + wrapper = shallow( ({ name: serverName })} toggle={toggleMock} isOpen={true} deleteServer={deleteServerMock} - history={Mock.of({ push })} />, ); }); @@ -48,7 +51,7 @@ describe('', () => { expect(toggleMock).toHaveBeenCalledTimes(1); expect(deleteServerMock).not.toHaveBeenCalled(); - expect(push).not.toHaveBeenCalled(); + expect(navigate).not.toHaveBeenCalled(); }); it('deletes server when clicking accept button', () => { @@ -58,6 +61,6 @@ describe('', () => { expect(toggleMock).toHaveBeenCalledTimes(1); expect(deleteServerMock).toHaveBeenCalledTimes(1); - expect(push).toHaveBeenCalledTimes(1); + expect(navigate).toHaveBeenCalledTimes(1); }); }); diff --git a/test/servers/EditServer.test.tsx b/test/servers/EditServer.test.tsx index 4f6a65da..abd60589 100644 --- a/test/servers/EditServer.test.tsx +++ b/test/servers/EditServer.test.tsx @@ -1,43 +1,34 @@ import { mount, ReactWrapper } from 'enzyme'; import { Mock } from 'ts-mockery'; -import { History, Location } from 'history'; -import { match } from 'react-router'; // eslint-disable-line @typescript-eslint/no-unused-vars +import { useNavigate } from 'react-router-dom'; import { EditServer as editServerConstruct } from '../../src/servers/EditServer'; import { ServerForm } from '../../src/servers/helpers/ServerForm'; import { ReachableServer } from '../../src/servers/data'; +jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useNavigate: jest.fn() })); + describe('', () => { let wrapper: ReactWrapper; const ServerError = jest.fn(); const editServerMock = jest.fn(); - const goBack = jest.fn(); - const historyMock = Mock.of({ goBack }); - const match = Mock.of>({ - params: { serverId: 'abc123' }, - }); + const navigate = jest.fn(); const selectedServer = Mock.of({ id: 'abc123', name: 'name', url: 'url', apiKey: 'apiKey', }); + const EditServer = editServerConstruct(ServerError); beforeEach(() => { - const EditServer = editServerConstruct(ServerError); + (useNavigate as any).mockReturnValue(navigate); wrapper = mount( - ()} - selectedServer={selectedServer} - selectServer={jest.fn()} - />, + , ); }); - afterEach(jest.resetAllMocks); + afterEach(jest.clearAllMocks); afterEach(() => wrapper?.unmount()); it('renders components', () => { @@ -50,6 +41,6 @@ describe('', () => { form.simulate('submit', {}); expect(editServerMock).toHaveBeenCalledTimes(1); - expect(goBack).toHaveBeenCalledTimes(1); + expect(navigate).toHaveBeenCalledWith(-1); }); }); diff --git a/test/short-urls/EditShortUrl.test.tsx b/test/short-urls/EditShortUrl.test.tsx index 51243bf7..5aa2eaa8 100644 --- a/test/short-urls/EditShortUrl.test.tsx +++ b/test/short-urls/EditShortUrl.test.tsx @@ -1,7 +1,6 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { Mock } from 'ts-mockery'; -import { History, Location } from 'history'; -import { match } from 'react-router'; // eslint-disable-line @typescript-eslint/no-unused-vars +import { useLocation, useParams } from 'react-router-dom'; import { EditShortUrl as createEditShortUrl } from '../../src/short-urls/EditShortUrl'; import { Settings } from '../../src/settings/reducers/settings'; import { ShortUrlDetail } from '../../src/short-urls/reducers/shortUrlDetail'; @@ -9,29 +8,32 @@ import { ShortUrlEdition } from '../../src/short-urls/reducers/shortUrlEdition'; import { ShlinkApiError } from '../../src/api/ShlinkApiError'; import { ShortUrl } from '../../src/short-urls/data'; +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: jest.fn().mockReturnValue(jest.fn()), + useParams: jest.fn().mockReturnValue({}), + useLocation: jest.fn().mockReturnValue({}), +})); + describe('', () => { let wrapper: ShallowWrapper; const ShortUrlForm = () => null; - const goBack = jest.fn(); const getShortUrlDetail = jest.fn(); const editShortUrl = jest.fn(async () => Promise.resolve()); const shortUrlCreation = { validateUrls: true }; + const EditShortUrl = createEditShortUrl(ShortUrlForm); const createWrapper = (detail: Partial = {}, edition: Partial = {}) => { - const EditSHortUrl = createEditShortUrl(ShortUrlForm); + (useParams as any).mockReturnValue({ shortCode: 'the_base_url' }); + (useLocation as any).mockReturnValue({ search: '' }); wrapper = shallow( - ({ shortUrlCreation })} selectedServer={null} shortUrlDetail={Mock.of(detail)} shortUrlEdition={Mock.of(edition)} getShortUrlDetail={getShortUrlDetail} editShortUrl={editShortUrl} - history={Mock.of({ goBack })} - location={Mock.all()} - match={Mock.of>({ - params: { shortCode: 'the_base_url' }, - })} />, ); diff --git a/test/short-urls/SearchBar.test.tsx b/test/short-urls/SearchBar.test.tsx index b8ccdc4c..2567b8e1 100644 --- a/test/short-urls/SearchBar.test.tsx +++ b/test/short-urls/SearchBar.test.tsx @@ -1,29 +1,30 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { Mock } from 'ts-mockery'; -import { History, Location } from 'history'; -import { match } from 'react-router'; import { formatISO } from 'date-fns'; -import filteringBarCreator, { ShortUrlsFilteringProps } from '../../src/short-urls/ShortUrlsFilteringBar'; +import { useLocation, useNavigate } from 'react-router-dom'; +import filteringBarCreator from '../../src/short-urls/ShortUrlsFilteringBar'; import SearchField from '../../src/utils/SearchField'; import Tag from '../../src/tags/helpers/Tag'; import { DateRangeSelector } from '../../src/utils/dates/DateRangeSelector'; import ColorGenerator from '../../src/utils/services/ColorGenerator'; -import { ShortUrlListRouteParams } from '../../src/short-urls/helpers/hooks'; + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: jest.fn(), + useParams: jest.fn().mockReturnValue({ serverId: '1' }), + useLocation: jest.fn().mockReturnValue({}), +})); describe('', () => { let wrapper: ShallowWrapper; const ShortUrlsFilteringBar = filteringBarCreator(Mock.all()); - const push = jest.fn(); + const navigate = jest.fn(); const now = new Date(); - const createWrapper = (props: Partial = {}) => { - wrapper = shallow( - ({ push })} - location={Mock.of({ search: '' })} - match={Mock.of>({ params: { serverId: '1' } })} - {...props} - />, - ); + const createWrapper = (search = '') => { + (useLocation as any).mockReturnValue({ search }); + (useNavigate as any).mockReturnValue(navigate); + + wrapper = shallow(); return wrapper; }; @@ -44,7 +45,7 @@ describe('', () => { [ '', 0 ], [ 'foo=bar', 0 ], ])('renders the proper amount of tags', (search, expectedTagComps) => { - const wrapper = createWrapper({ location: Mock.of({ search }) }); + const wrapper = createWrapper(search); expect(wrapper.find(Tag)).toHaveLength(expectedTagComps); }); @@ -53,18 +54,18 @@ describe('', () => { const wrapper = createWrapper(); const searchField = wrapper.find(SearchField); - expect(push).not.toHaveBeenCalled(); + expect(navigate).not.toHaveBeenCalled(); searchField.simulate('change', 'search-term'); - expect(push).toHaveBeenCalledWith('/server/1/list-short-urls/1?search=search-term'); + expect(navigate).toHaveBeenCalledWith('/server/1/list-short-urls/1?search=search-term'); }); it('redirects to first page when a tag is removed', () => { - const wrapper = createWrapper({ location: Mock.of({ search: 'tags=foo,bar' }) }); + const wrapper = createWrapper('tags=foo,bar'); const tag = wrapper.find(Tag).first(); - expect(push).not.toHaveBeenCalled(); + expect(navigate).not.toHaveBeenCalled(); tag.simulate('close'); - expect(push).toHaveBeenCalledWith('/server/1/list-short-urls/1?tags=bar'); + expect(navigate).toHaveBeenCalledWith('/server/1/list-short-urls/1?tags=bar'); }); it.each([ @@ -78,8 +79,8 @@ describe('', () => { const wrapper = createWrapper(); const dateRange = wrapper.find(DateRangeSelector); - expect(push).not.toHaveBeenCalled(); + expect(navigate).not.toHaveBeenCalled(); dateRange.simulate('datesChange', dates); - expect(push).toHaveBeenCalledWith(`/server/1/list-short-urls/1?${expectedQuery}`); + expect(navigate).toHaveBeenCalledWith(`/server/1/list-short-urls/1?${expectedQuery}`); }); }); diff --git a/test/short-urls/ShortUrlsList.test.tsx b/test/short-urls/ShortUrlsList.test.tsx index 9f9cb65f..8ce2ffbc 100644 --- a/test/short-urls/ShortUrlsList.test.tsx +++ b/test/short-urls/ShortUrlsList.test.tsx @@ -1,8 +1,7 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { ReactElement } from 'react'; import { Mock } from 'ts-mockery'; -import { History, Location } from 'history'; -import { match } from 'react-router'; +import { useNavigate } from 'react-router-dom'; import shortUrlsListCreator from '../../src/short-urls/ShortUrlsList'; import { ShortUrlsOrderableFields, ShortUrl, ShortUrlsOrder } from '../../src/short-urls/data'; import { MercureBoundProps } from '../../src/mercure/helpers/boundToMercureHub'; @@ -10,15 +9,21 @@ import { ShortUrlsList as ShortUrlsListModel } from '../../src/short-urls/reduce import { OrderingDropdown } from '../../src/utils/OrderingDropdown'; import Paginator from '../../src/short-urls/Paginator'; import { ReachableServer } from '../../src/servers/data'; -import { ShortUrlListRouteParams } from '../../src/short-urls/helpers/hooks'; import { Settings } from '../../src/settings/reducers/settings'; +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: jest.fn().mockReturnValue(jest.fn()), + useParams: jest.fn().mockReturnValue({}), + useLocation: jest.fn().mockReturnValue({ search: '?tags=test%20tag&search=example.com' }), +})); + describe('', () => { let wrapper: ShallowWrapper; const ShortUrlsTable = () => null; const ShortUrlsFilteringBar = () => null; const listShortUrlsMock = jest.fn(); - const push = jest.fn(); + const navigate = jest.fn(); const shortUrlsList = Mock.of({ shortUrls: { data: [ @@ -36,20 +41,19 @@ describe('', () => { ({ mercureInfo: { loading: true } })} listShortUrls={listShortUrlsMock} - match={Mock.of>({ params: {} })} - location={Mock.of({ search: '?tags=test%20tag&search=example.com' })} shortUrlsList={shortUrlsList} - history={Mock.of({ push })} selectedServer={Mock.of({ id: '1' })} settings={Mock.of({ shortUrlsList: { defaultOrdering } })} />, ).dive(); // Dive is needed as this component is wrapped in a HOC beforeEach(() => { + (useNavigate as any).mockReturnValue(navigate); + wrapper = createWrapper(); }); - afterEach(jest.resetAllMocks); + afterEach(jest.clearAllMocks); afterEach(() => wrapper?.unmount()); it('wraps expected components', () => { @@ -68,10 +72,10 @@ describe('', () => { wrapper.find(ShortUrlsTable).simulate('tagClick', 'bar'); wrapper.find(ShortUrlsTable).simulate('tagClick', 'baz'); - expect(push).toHaveBeenCalledTimes(3); - expect(push).toHaveBeenNthCalledWith(1, expect.stringContaining(`tags=${encodeURIComponent('test tag,foo')}`)); - expect(push).toHaveBeenNthCalledWith(2, expect.stringContaining(`tags=${encodeURIComponent('test tag,bar')}`)); - expect(push).toHaveBeenNthCalledWith(3, expect.stringContaining(`tags=${encodeURIComponent('test tag,baz')}`)); + expect(navigate).toHaveBeenCalledTimes(3); + expect(navigate).toHaveBeenNthCalledWith(1, expect.stringContaining(`tags=${encodeURIComponent('test tag,foo')}`)); + expect(navigate).toHaveBeenNthCalledWith(2, expect.stringContaining(`tags=${encodeURIComponent('test tag,bar')}`)); + expect(navigate).toHaveBeenNthCalledWith(3, expect.stringContaining(`tags=${encodeURIComponent('test tag,baz')}`)); }); it('invokes order icon rendering', () => { diff --git a/test/tags/TagsTable.test.tsx b/test/tags/TagsTable.test.tsx index 4d5dc344..ef8315af 100644 --- a/test/tags/TagsTable.test.tsx +++ b/test/tags/TagsTable.test.tsx @@ -1,13 +1,14 @@ import { Mock } from 'ts-mockery'; import { shallow, ShallowWrapper } from 'enzyme'; -import { match } from 'react-router'; -import { Location, History } from 'history'; +import { useLocation } from 'react-router-dom'; import { TagsTable as createTagsTable } from '../../src/tags/TagsTable'; import { SelectedServer } from '../../src/servers/data'; import { rangeOf } from '../../src/utils/utils'; import SimplePaginator from '../../src/common/SimplePaginator'; import { NormalizedTag } from '../../src/tags/data'; +jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useLocation: jest.fn() })); + describe('', () => { const TagsTableRow = () => null; const orderByColumn = jest.fn(); @@ -15,14 +16,13 @@ describe('', () => { const tags = (amount: number) => rangeOf(amount, (i) => `tag_${i}`); let wrapper: ShallowWrapper; const createWrapper = (sortedTags: string[] = [], search = '') => { + (useLocation as any).mockReturnValue({ search }); + wrapper = shallow( Mock.of({ tag }))} selectedServer={Mock.all()} currentOrder={{}} - history={Mock.all()} - location={Mock.of({ search })} - match={Mock.all()} orderByColumn={() => orderByColumn} />, ); @@ -30,11 +30,6 @@ describe('', () => { return wrapper; }; - beforeEach(() => { - (global as any).location = { search: '', pathname: '' }; - (global as any).history = { pushState: jest.fn() }; - }); - afterEach(jest.clearAllMocks); afterEach(() => wrapper?.unmount()); diff --git a/test/visits/NonOrphanVisits.test.tsx b/test/visits/NonOrphanVisits.test.tsx index 813d4e89..c1af476b 100644 --- a/test/visits/NonOrphanVisits.test.tsx +++ b/test/visits/NonOrphanVisits.test.tsx @@ -1,7 +1,5 @@ import { shallow } from 'enzyme'; import { Mock } from 'ts-mockery'; -import { History, Location } from 'history'; -import { match } from 'react-router'; import { NonOrphanVisits as createNonOrphanVisits } from '../../src/visits/NonOrphanVisits'; import { MercureBoundProps } from '../../src/mercure/helpers/boundToMercureHub'; import { VisitsInfo } from '../../src/visits/types'; @@ -11,9 +9,14 @@ import { Settings } from '../../src/settings/reducers/settings'; import { VisitsExporter } from '../../src/visits/services/VisitsExporter'; import { SelectedServer } from '../../src/servers/data'; +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: jest.fn().mockReturnValue(jest.fn()), + useParams: jest.fn().mockReturnValue({}), +})); + describe('', () => { it('wraps visits stats and header', () => { - const goBack = jest.fn(); const getNonOrphanVisits = jest.fn(); const cancelGetNonOrphanVisits = jest.fn(); const nonOrphanVisits = Mock.all(); @@ -25,9 +28,6 @@ describe('', () => { getNonOrphanVisits={getNonOrphanVisits} nonOrphanVisits={nonOrphanVisits} cancelGetNonOrphanVisits={cancelGetNonOrphanVisits} - history={Mock.of({ goBack })} - location={Mock.all()} - match={Mock.of({ url: 'the_base_url' })} settings={Mock.all()} selectedServer={Mock.all()} />, @@ -39,9 +39,8 @@ describe('', () => { expect(header).toHaveLength(1); expect(stats.prop('cancelGetVisits')).toEqual(cancelGetNonOrphanVisits); expect(stats.prop('visitsInfo')).toEqual(nonOrphanVisits); - expect(stats.prop('baseUrl')).toEqual('the_base_url'); expect(stats.prop('isOrphanVisits')).not.toBeDefined(); expect(header.prop('nonOrphanVisits')).toEqual(nonOrphanVisits); - expect(header.prop('goBack')).toEqual(goBack); + expect(header.prop('goBack')).toEqual(expect.any(Function)); }); }); diff --git a/test/visits/OrphanVisits.test.tsx b/test/visits/OrphanVisits.test.tsx index e1f6dc78..cae65bda 100644 --- a/test/visits/OrphanVisits.test.tsx +++ b/test/visits/OrphanVisits.test.tsx @@ -1,7 +1,5 @@ import { shallow } from 'enzyme'; import { Mock } from 'ts-mockery'; -import { History, Location } from 'history'; -import { match } from 'react-router'; import { OrphanVisits as createOrphanVisits } from '../../src/visits/OrphanVisits'; import { MercureBoundProps } from '../../src/mercure/helpers/boundToMercureHub'; import { VisitsInfo } from '../../src/visits/types'; @@ -11,9 +9,14 @@ import { Settings } from '../../src/settings/reducers/settings'; import { VisitsExporter } from '../../src/visits/services/VisitsExporter'; import { SelectedServer } from '../../src/servers/data'; +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: jest.fn().mockReturnValue(jest.fn()), + useParams: jest.fn().mockReturnValue({}), +})); + describe('', () => { it('wraps visits stats and header', () => { - const goBack = jest.fn(); const getOrphanVisits = jest.fn(); const cancelGetOrphanVisits = jest.fn(); const orphanVisits = Mock.all(); @@ -25,9 +28,6 @@ describe('', () => { getOrphanVisits={getOrphanVisits} orphanVisits={orphanVisits} cancelGetOrphanVisits={cancelGetOrphanVisits} - history={Mock.of({ goBack })} - location={Mock.all()} - match={Mock.of({ url: 'the_base_url' })} settings={Mock.all()} selectedServer={Mock.all()} />, @@ -39,9 +39,8 @@ describe('', () => { expect(header).toHaveLength(1); expect(stats.prop('cancelGetVisits')).toEqual(cancelGetOrphanVisits); expect(stats.prop('visitsInfo')).toEqual(orphanVisits); - expect(stats.prop('baseUrl')).toEqual('the_base_url'); expect(stats.prop('isOrphanVisits')).toEqual(true); expect(header.prop('orphanVisits')).toEqual(orphanVisits); - expect(header.prop('goBack')).toEqual(goBack); + expect(header.prop('goBack')).toEqual(expect.any(Function)); }); }); diff --git a/test/visits/ShortUrlVisits.test.tsx b/test/visits/ShortUrlVisits.test.tsx index 6a85a838..c70ac4b2 100644 --- a/test/visits/ShortUrlVisits.test.tsx +++ b/test/visits/ShortUrlVisits.test.tsx @@ -1,8 +1,6 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { identity } from 'ramda'; import { Mock } from 'ts-mockery'; -import { History, Location } from 'history'; -import { match } from 'react-router'; // eslint-disable-line @typescript-eslint/no-unused-vars import createShortUrlVisits, { ShortUrlVisitsProps } from '../../src/visits/ShortUrlVisits'; import ShortUrlVisitsHeader from '../../src/visits/ShortUrlVisitsHeader'; import { ShortUrlVisits as ShortUrlVisitsState } from '../../src/visits/reducers/shortUrlVisits'; @@ -11,16 +9,16 @@ import VisitsStats from '../../src/visits/VisitsStats'; import { MercureBoundProps } from '../../src/mercure/helpers/boundToMercureHub'; import { VisitsExporter } from '../../src/visits/services/VisitsExporter'; +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: jest.fn().mockReturnValue(jest.fn()), + useLocation: jest.fn().mockReturnValue({ search: '' }), + useParams: jest.fn().mockReturnValue({ shortCode: 'abc123' }), +})); + describe('', () => { let wrapper: ShallowWrapper; const getShortUrlVisitsMock = jest.fn(); - const match = Mock.of>({ - params: { shortCode: 'abc123' }, - }); - const location = Mock.of({ search: '' }); - const history = Mock.of({ - goBack: jest.fn(), - }); const ShortUrlVisits = createShortUrlVisits(Mock.all()); beforeEach(() => { @@ -30,9 +28,6 @@ describe('', () => { {...Mock.of({ mercureInfo: {} })} getShortUrlDetail={identity} getShortUrlVisits={getShortUrlVisitsMock} - match={match} - location={location} - history={history} shortUrlVisits={Mock.of({ loading: true, visits: [] })} shortUrlDetail={Mock.all()} cancelGetShortUrlVisits={() => {}} @@ -40,8 +35,8 @@ describe('', () => { ).dive(); // Dive is needed as this component is wrapped in a HOC }); + afterEach(jest.clearAllMocks); afterEach(() => wrapper.unmount()); - afterEach(jest.resetAllMocks); it('renders visit stats and visits header', () => { const visitStats = wrapper.find(VisitsStats); diff --git a/test/visits/TagVisits.test.tsx b/test/visits/TagVisits.test.tsx index ca00f9e4..10d60d6a 100644 --- a/test/visits/TagVisits.test.tsx +++ b/test/visits/TagVisits.test.tsx @@ -1,7 +1,5 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { Mock } from 'ts-mockery'; -import { History } from 'history'; -import { match } from 'react-router'; // eslint-disable-line @typescript-eslint/no-unused-vars import createTagVisits, { TagVisitsProps } from '../../src/visits/TagVisits'; import TagVisitsHeader from '../../src/visits/TagVisitsHeader'; import ColorGenerator from '../../src/utils/services/ColorGenerator'; @@ -10,15 +8,16 @@ import VisitsStats from '../../src/visits/VisitsStats'; import { MercureBoundProps } from '../../src/mercure/helpers/boundToMercureHub'; import { VisitsExporter } from '../../src/visits/services/VisitsExporter'; +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: jest.fn().mockReturnValue(jest.fn()), + useLocation: jest.fn().mockReturnValue({}), + useParams: jest.fn().mockReturnValue({ tag: 'foo' }), +})); + describe('', () => { let wrapper: ShallowWrapper; const getTagVisitsMock = jest.fn(); - const match = Mock.of>({ - params: { tag: 'foo' }, - }); - const history = Mock.of({ - goBack: jest.fn(), - }); beforeEach(() => { const TagVisits = createTagVisits(Mock.all(), Mock.all()); @@ -28,8 +27,6 @@ describe('', () => { {...Mock.all()} {...Mock.of({ mercureInfo: {} })} getTagVisits={getTagVisitsMock} - match={match} - history={history} tagVisits={Mock.of({ loading: true, visits: [] })} cancelGetTagVisits={() => {}} />, diff --git a/test/visits/VisitsStats.test.tsx b/test/visits/VisitsStats.test.tsx index 32f1e1f3..1fe1f5c5 100644 --- a/test/visits/VisitsStats.test.tsx +++ b/test/visits/VisitsStats.test.tsx @@ -1,6 +1,8 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { Button, Progress } from 'reactstrap'; +import { sum } from 'ramda'; import { Mock } from 'ts-mockery'; +import { Route } from 'react-router-dom'; import VisitStats from '../../src/visits/VisitsStats'; import Message from '../../src/utils/Message'; import { Visit, VisitsInfo } from '../../src/visits/types'; @@ -12,7 +14,7 @@ import { SelectedServer } from '../../src/servers/data'; import { SortableBarChartCard } from '../../src/visits/charts/SortableBarChartCard'; import { DoughnutChartCard } from '../../src/visits/charts/DoughnutChartCard'; -describe('', () => { +describe('', () => { const visits = [ Mock.all(), Mock.all(), Mock.all() ]; let wrapper: ShallowWrapper; @@ -25,7 +27,6 @@ describe('', () => { getVisits={getVisitsMock} visitsInfo={Mock.of(visitsInfo)} cancelGetVisits={() => {}} - baseUrl={''} settings={Mock.all()} exportCsv={exportCsv} selectedServer={Mock.all()} @@ -76,18 +77,27 @@ describe('', () => { it('renders expected amount of charts', () => { const wrapper = createComponent({ loading: false, error: false, visits }); - const charts = wrapper.find(DoughnutChartCard); - const sortableCharts = wrapper.find(SortableBarChartCard); - const lineChart = wrapper.find(LineChartCard); - const table = wrapper.find(VisitsTable); + const total = sum(wrapper.find(Route).map((element) => { + const ElementComponents = () => element.prop('element'); + // @ts-expect-error Wrapped element + const wrappedElement = shallow(); - expect(charts.length + sortableCharts.length + lineChart.length).toEqual(6); - expect(table).toHaveLength(1); + const charts = wrappedElement.find(DoughnutChartCard); + const sortableCharts = wrappedElement.find(SortableBarChartCard); + const lineChart = wrappedElement.find(LineChartCard); + const table = wrappedElement.find(VisitsTable); + + return charts.length + sortableCharts.length + lineChart.length + table.length; + })); + + expect(total).toEqual(7); }); it('holds the map button content generator on cities chart extraHeaderContent', () => { const wrapper = createComponent({ loading: false, error: false, visits }); - const citiesChart = wrapper.find(SortableBarChartCard).find('[title="Cities"]'); + const ElementComponent = () => wrapper.find(Route).findWhere((element) => element.prop('path') === 'by-location') + .prop('element'); + const citiesChart = shallow().find(SortableBarChartCard).find('[title="Cities"]'); const extraHeaderContent = citiesChart.prop('extraHeaderContent'); expect(extraHeaderContent).toHaveLength(1);