diff --git a/package-lock.json b/package-lock.json index 573cd9837c..4042640011 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1348,9 +1348,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", - "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", + "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", "requires": { "regenerator-runtime": "^0.13.11" } @@ -1413,9 +1413,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", - "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", + "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", "requires": { "regenerator-runtime": "^0.13.11" } @@ -1458,9 +1458,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", - "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", + "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", "requires": { "regenerator-runtime": "^0.13.11" } @@ -2388,6 +2388,93 @@ "stylelint-scss": "^3.17.2" } }, + "@material-ui/core": { + "version": "4.12.4", + "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.12.4.tgz", + "integrity": "sha512-tr7xekNlM9LjA6pagJmL8QCgZXaubWUwkJnoYcMKd4gw/t4XiyvnTkjdGrUVicyB2BsdaAv1tvow45bPM4sSwQ==", + "requires": { + "@babel/runtime": "^7.4.4", + "@material-ui/styles": "^4.11.5", + "@material-ui/system": "^4.12.2", + "@material-ui/types": "5.1.0", + "@material-ui/utils": "^4.11.3", + "@types/react-transition-group": "^4.2.0", + "clsx": "^1.0.4", + "hoist-non-react-statics": "^3.3.2", + "popper.js": "1.16.1-lts", + "prop-types": "^15.7.2", + "react-is": "^16.8.0 || ^17.0.0", + "react-transition-group": "^4.4.0" + } + }, + "@material-ui/styles": { + "version": "4.11.5", + "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.5.tgz", + "integrity": "sha512-o/41ot5JJiUsIETME9wVLAJrmIWL3j0R0Bj2kCOLbSfqEkKf0fmaPt+5vtblUh5eXr2S+J/8J3DaCb10+CzPGA==", + "requires": { + "@babel/runtime": "^7.4.4", + "@emotion/hash": "^0.8.0", + "@material-ui/types": "5.1.0", + "@material-ui/utils": "^4.11.3", + "clsx": "^1.0.4", + "csstype": "^2.5.2", + "hoist-non-react-statics": "^3.3.2", + "jss": "^10.5.1", + "jss-plugin-camel-case": "^10.5.1", + "jss-plugin-default-unit": "^10.5.1", + "jss-plugin-global": "^10.5.1", + "jss-plugin-nested": "^10.5.1", + "jss-plugin-props-sort": "^10.5.1", + "jss-plugin-rule-value-function": "^10.5.1", + "jss-plugin-vendor-prefixer": "^10.5.1", + "prop-types": "^15.7.2" + }, + "dependencies": { + "@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" + }, + "csstype": { + "version": "2.6.21", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", + "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" + } + } + }, + "@material-ui/system": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.12.2.tgz", + "integrity": "sha512-6CSKu2MtmiJgcCGf6nBQpM8fLkuB9F55EKfbdTC80NND5wpTmKzwdhLYLH3zL4cLlK0gVaaltW7/wMuyTnN0Lw==", + "requires": { + "@babel/runtime": "^7.4.4", + "@material-ui/utils": "^4.11.3", + "csstype": "^2.5.2", + "prop-types": "^15.7.2" + }, + "dependencies": { + "csstype": { + "version": "2.6.21", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", + "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" + } + } + }, + "@material-ui/types": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz", + "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==" + }, + "@material-ui/utils": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.3.tgz", + "integrity": "sha512-ZuQPV4rBK/V1j2dIkSSEcH5uT6AaHuKWFfotADHsC0wVL1NLd2WkFCm4ZZbX33iO4ydl6V0GPngKm8HZQ2oujg==", + "requires": { + "@babel/runtime": "^7.4.4", + "prop-types": "^15.7.2", + "react-is": "^16.8.0 || ^17.0.0" + } + }, "@mui/base": { "version": "5.0.0-beta.4", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.4.tgz", @@ -2917,6 +3004,24 @@ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" }, + "@reduxjs/toolkit": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz", + "integrity": "sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==", + "requires": { + "immer": "^9.0.21", + "redux": "^4.2.1", + "redux-thunk": "^2.4.2", + "reselect": "^4.1.8" + }, + "dependencies": { + "immer": { + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==" + } + } + }, "@remix-run/router": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.5.0.tgz", @@ -3212,6 +3317,15 @@ "@types/node": "*" } }, + "@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", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "requires": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "@types/html-minifier-terser": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz", @@ -3394,6 +3508,11 @@ "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", "dev": true }, + "@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "@types/webpack": { "version": "4.41.32", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.32.tgz", @@ -5919,6 +6038,15 @@ } } }, + "css-vendor": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", + "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==", + "requires": { + "@babel/runtime": "^7.8.3", + "is-in-browser": "^1.0.2" + } + }, "css-what": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", @@ -10145,6 +10273,11 @@ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==" }, + "hyphenate-style-name": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", + "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -10602,6 +10735,11 @@ "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", "dev": true }, + "is-in-browser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", + "integrity": "sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==" + }, "is-installed-globally": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", @@ -12491,6 +12629,84 @@ "verror": "1.10.0" } }, + "jss": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.10.0.tgz", + "integrity": "sha512-cqsOTS7jqPsPMjtKYDUpdFC0AbhYFLTcuGRqymgmdJIeQ8cH7+AgX7YSgQy79wXloZq2VvATYxUOUQEvS1V/Zw==", + "requires": { + "@babel/runtime": "^7.3.1", + "csstype": "^3.0.2", + "is-in-browser": "^1.1.3", + "tiny-warning": "^1.0.2" + } + }, + "jss-plugin-camel-case": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.10.0.tgz", + "integrity": "sha512-z+HETfj5IYgFxh1wJnUAU8jByI48ED+v0fuTuhKrPR+pRBYS2EDwbusU8aFOpCdYhtRc9zhN+PJ7iNE8pAWyPw==", + "requires": { + "@babel/runtime": "^7.3.1", + "hyphenate-style-name": "^1.0.3", + "jss": "10.10.0" + } + }, + "jss-plugin-default-unit": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.10.0.tgz", + "integrity": "sha512-SvpajxIECi4JDUbGLefvNckmI+c2VWmP43qnEy/0eiwzRUsafg5DVSIWSzZe4d2vFX1u9nRDP46WCFV/PXVBGQ==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.10.0" + } + }, + "jss-plugin-global": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.10.0.tgz", + "integrity": "sha512-icXEYbMufiNuWfuazLeN+BNJO16Ge88OcXU5ZDC2vLqElmMybA31Wi7lZ3lf+vgufRocvPj8443irhYRgWxP+A==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.10.0" + } + }, + "jss-plugin-nested": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.10.0.tgz", + "integrity": "sha512-9R4JHxxGgiZhurDo3q7LdIiDEgtA1bTGzAbhSPyIOWb7ZubrjQe8acwhEQ6OEKydzpl8XHMtTnEwHXCARLYqYA==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.10.0", + "tiny-warning": "^1.0.2" + } + }, + "jss-plugin-props-sort": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.10.0.tgz", + "integrity": "sha512-5VNJvQJbnq/vRfje6uZLe/FyaOpzP/IH1LP+0fr88QamVrGJa0hpRRyAa0ea4U/3LcorJfBFVyC4yN2QC73lJg==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.10.0" + } + }, + "jss-plugin-rule-value-function": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.10.0.tgz", + "integrity": "sha512-uEFJFgaCtkXeIPgki8ICw3Y7VMkL9GEan6SqmT9tqpwM+/t+hxfMUdU4wQ0MtOiMNWhwnckBV0IebrKcZM9C0g==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.10.0", + "tiny-warning": "^1.0.2" + } + }, + "jss-plugin-vendor-prefixer": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.10.0.tgz", + "integrity": "sha512-UY/41WumgjW8r1qMCO8l1ARg7NHnfRVWRhZ2E2m0DMYsr2DD91qIXLyNhiX83hHswR7Wm4D+oDYNC1zWCJWtqg==", + "requires": { + "@babel/runtime": "^7.3.1", + "css-vendor": "^2.0.8", + "jss": "10.10.0" + } + }, "jsx-ast-utils": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.2.tgz", @@ -14706,6 +14922,11 @@ "ts-pnp": "^1.1.6" } }, + "popper.js": { + "version": "1.16.1-lts", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1-lts.tgz", + "integrity": "sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA==" + }, "portfinder": { "version": "1.0.28", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", @@ -16271,6 +16492,26 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "react-redux": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.2.tgz", + "integrity": "sha512-xJKYI189VwfsFc4CJvHqHlDrzyFTY/3vZACbE+rr/zQ34Xx1wQfB4OTOSeOSNrF6BDVe8OOdxIrAnMGXA3ggfw==", + "requires": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "dependencies": { + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + } + } + }, "react-refresh": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", @@ -16483,6 +16724,19 @@ "strip-indent": "^3.0.0" } }, + "redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "requires": { + "@babel/runtime": "^7.9.2" + } + }, + "redux-thunk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", + "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==" + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -16811,6 +17065,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, + "reselect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==" + }, "resolve": { "version": "1.18.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", @@ -18857,6 +19116,11 @@ "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" }, + "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==" + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -19366,6 +19630,11 @@ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, + "use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==" + }, "util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", diff --git a/package.json b/package.json index 17a1b78e97..5e717e2616 100755 --- a/package.json +++ b/package.json @@ -12,12 +12,15 @@ "@fortawesome/fontawesome-svg-core": "^6.4.0", "@fortawesome/free-solid-svg-icons": "^6.4.0", "@fortawesome/react-fontawesome": "^0.2.0", + "@material-ui/core": "^4.12.4", "@mui/material": "^5.13.4", + "@reduxjs/toolkit": "^1.9.5", "@uidotdev/usehooks": "^2.0.1", "bulma": "^0.9.3", "classnames": "^2.3.2", "react": "^17.0.2", "react-dom": "^17.0.2", + "react-redux": "^8.1.2", "react-router-dom": "^6.10.0", "react-scripts": "^4.0.3" }, diff --git a/src/App.tsx b/src/App.tsx index 2bca47014c..765315364a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,36 +1,34 @@ -import { useState, useEffect } from 'react'; +import { useEffect } from 'react'; import { Navigate, Route, Routes } from 'react-router-dom'; import { Footer } from './components/Footer/Footer'; import { Header } from './components/Header/Header'; -import { Homepage } from './components/Homepage/Homepage'; +import { Homepage } from './pages/Homepage'; import { ProductDetailsPage, -} from './components/ProductDetailsPage/ProductDetailsPage'; +} from './components/ProductDetails/ProductDetailsPage'; import { API_URL, getProducts } from './helpers/helper'; -import { Product } from './types/Products'; import './App.scss'; import { Favoutires } from './components/Favourites/Favourites'; import { Cart } from './components/Cart/Cart'; -import { - ProductDataContext, -} from './components/ProductDataContext/ProductDataContext'; import { ScrollToTop } from './helpers/ScrollToTop'; -import { PhonesPage } from './components/ProductPage/PhonesPage/PhonesPage'; -import { TabletsPage } from './components/ProductPage/TabletsPage/TabletsPage'; +import { PhonesPage } from './pages/PhonesPage'; +import { TabletsPage } from './pages/TabletsPage'; import { AccessoriesPage, -} from './components/ProductPage/AccessoriesPage/AccessoriesPage'; +} from './pages/AccessoriesPage'; import { NotFound } from './components/NotFound/NotFound'; +import { useAppDispatch } from './app/hooks'; +import { set } from './features/productsSlice'; const App = () => { - const [products, setProducts] = useState([]); + const dispatch = useAppDispatch(); useEffect(() => { async function fetchData() { try { const response = await getProducts(API_URL); - setProducts(response); + dispatch(set(response)); } catch (fetchError) { throw new Error('Data could not be fetched'); } @@ -44,48 +42,46 @@ const App = () => {
- - - } - /> - - } /> - } - /> - - - } /> - } - /> - - - } /> - } - /> - + + } + /> + + } /> } + path=":productId" + element={} /> + + + } /> } + path=":productId" + element={} /> - } /> + + + } /> } + path=":productId" + element={} /> - - + + } + /> + } + /> + } /> + } + /> +
diff --git a/src/app/hooks.ts b/src/app/hooks.ts new file mode 100644 index 0000000000..d4e3ffbc64 --- /dev/null +++ b/src/app/hooks.ts @@ -0,0 +1,6 @@ +import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; +import type { RootState, AppDispatch } from './store'; + +// Use these hooks everywhere instead of useDispatch and useSelector +export const useAppDispatch = () => useDispatch(); +export const useAppSelector: TypedUseSelectorHook = useSelector; diff --git a/src/app/store.ts b/src/app/store.ts new file mode 100644 index 0000000000..c83ca42632 --- /dev/null +++ b/src/app/store.ts @@ -0,0 +1,22 @@ +import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit'; +import productsSlice from '../features/productsSlice'; +import productInfoSlice from '../features/productInfoSlice'; + +export const store = configureStore({ + reducer: { + products: productsSlice, + productInfo: productInfoSlice, + }, +}); + +export type AppDispatch = typeof store.dispatch; +export type RootState = ReturnType; + +/* eslint-disable @typescript-eslint/indent */ +export type AppThunk = ThunkAction< + ReturnType, + RootState, + unknown, + Action +>; +/* eslint-enable @typescript-eslint/indent */ diff --git a/src/components/Cart/Cart.tsx b/src/components/Cart/Cart.tsx index eaee30b7cb..c9c982ef04 100644 --- a/src/components/Cart/Cart.tsx +++ b/src/components/Cart/Cart.tsx @@ -18,6 +18,12 @@ export const Cart: React.FC = () => { const cartFromStorage = retrievedData ? JSON.parse(retrievedData) : []; setCartPhones(cartFromStorage); + + return () => { + if (timeoutId) { + clearTimeout(timeoutId); + } + }; }, []); const calculateTotal = (cartArray: Product[]) => { @@ -55,14 +61,6 @@ export const Cart: React.FC = () => { }, 3000); }; - useEffect(() => { - return () => { - if (timeoutId) { - clearTimeout(timeoutId); - } - }; - }, []); - return (
diff --git a/src/components/Favourites/Favourites.tsx b/src/components/Favourites/Favourites.tsx index 924fb8b442..a83bb14bb3 100644 --- a/src/components/Favourites/Favourites.tsx +++ b/src/components/Favourites/Favourites.tsx @@ -1,7 +1,7 @@ -import { useState } from 'react'; +import { useMemo, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { ProductCard } from '../ProductCard/ProductCard'; -import { PageIndicator } from '../PageIndicator/Phonespage/PageIndicator'; +import { PageIndicator } from '../PageIndicator/PageIndicator'; import { Product } from '../../types/Products'; import { NoResults } from '../NoResults/NoResults'; @@ -12,10 +12,13 @@ export const Favoutires: React.FC = () => { const [searchParams] = useSearchParams(); const query = searchParams.get('query'); - const filteredByQueryPhones = favouritePhones.filter((product) => ( - query === null - || query === '' - || product.name.toLowerCase().includes(query.toLowerCase()))); + const filteredByQueryPhones = useMemo(() => { + return favouritePhones.filter((product) => ( + query === null + || query === '' + || product.name.toLowerCase().includes(query.toLowerCase()) + )); + }, [favouritePhones, query]); return (
diff --git a/src/components/Homepage/Homepage.tsx b/src/components/Homepage/Homepage.tsx deleted file mode 100644 index e70f801b82..0000000000 --- a/src/components/Homepage/Homepage.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Product } from '../../types/Products'; -import { HotPrices } from '../HotPrices/HotPrices'; -import { MainSlider } from '../MainSlider/MainSlider'; - -type Props = { - products: Product[], -}; - -export const Homepage: React.FC = ({ products }) => { - return ( -
- - -
- ); -}; diff --git a/src/components/HotPrices/HotPrices.tsx b/src/components/HotPrices/HotPrices.tsx deleted file mode 100644 index 5133876dfe..0000000000 --- a/src/components/HotPrices/HotPrices.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React, { useMemo } from 'react'; -import { Product } from '../../types/Products'; -import { ProductsSlider } from '../ProductSlider/ProductSlider'; -import { ShopCategory } from '../ShopCategory/ShopCategory'; - -type Props = { - products: Product[], -}; - -export const HotPrices: React.FC = ({ products }) => { - const getHotPriceProducts = useMemo(() => { - return [...products] - .sort((a, b) => (b.fullPrice - b.price) - (a.fullPrice - a.price)); - }, [products]); - - const getBrandNewProducts = useMemo(() => { - return [...products].sort((a, b) => b.price - a.price); - }, [products]); - - return ( -
- - - -
- ); -}; diff --git a/src/components/NoResults/NoResults.tsx b/src/components/NoResults/NoResults.tsx index f853b1e45e..2f5aaccf6a 100644 --- a/src/components/NoResults/NoResults.tsx +++ b/src/components/NoResults/NoResults.tsx @@ -1,17 +1,13 @@ import './NoResults.scss'; export const NoResults: React.FC<{ category: string }> = ({ category }) => { - if (category === 'cart') { - return ( -
Your cart is empty
- ); - } - return (
- {category} - {' '} - not found + {category === 'cart' ? ( + 'Your cart is empty' + ) : ( + `${category} not found` + )}
); }; diff --git a/src/components/PageIndicator/PageIndicator.scss b/src/components/PageIndicator/PageIndicator.scss new file mode 100644 index 0000000000..d0cff12729 --- /dev/null +++ b/src/components/PageIndicator/PageIndicator.scss @@ -0,0 +1,6 @@ +.indicator-name { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 40px; +} diff --git a/src/components/PageIndicator/Phonespage/PageIndicator.tsx b/src/components/PageIndicator/PageIndicator.tsx similarity index 57% rename from src/components/PageIndicator/Phonespage/PageIndicator.tsx rename to src/components/PageIndicator/PageIndicator.tsx index 8b803fafcb..a49a42b84e 100644 --- a/src/components/PageIndicator/Phonespage/PageIndicator.tsx +++ b/src/components/PageIndicator/PageIndicator.tsx @@ -1,6 +1,5 @@ import { Link } from 'react-router-dom'; -import home from '../../../images/Home.svg'; -import arrow from '../../../images/Vector (Stroke).svg'; +import home from '../../images/Home.svg'; import './PageIndicator.scss'; type Props = { @@ -21,24 +20,11 @@ export const PageIndicator: React.FC = ({ home
- arrowBack - - {productType} - +
<
+ {productType} {productName && ( <> - arrowBack +
<
{productName}
)} diff --git a/src/components/PageIndicator/Phonespage/PageIndicator.scss b/src/components/PageIndicator/Phonespage/PageIndicator.scss deleted file mode 100644 index 3c881242ab..0000000000 --- a/src/components/PageIndicator/Phonespage/PageIndicator.scss +++ /dev/null @@ -1,13 +0,0 @@ -.indicator-name { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 40px; - - &__title { - font-size: 12px; - font-weight: 600; - line-height: 15px; - color: rgba(137, 147, 154, 1); - } -} diff --git a/src/components/Pagination/Pagination.tsx b/src/components/Pagination/Pagination.tsx index 49e959ba2d..517bbca9d5 100644 --- a/src/components/Pagination/Pagination.tsx +++ b/src/components/Pagination/Pagination.tsx @@ -58,12 +58,13 @@ export const Pagination: React.FC = ({ < - {pages.map((page) => ( + {pages.map((page, index) => ( typeof page === 'number' ? ( - - - -
- {isLoading ? ( - - ) : ( -
- - -
{productInfo.name}
- -
-
-
-
About
- {productInfo.description.map(product => ( - -
{product.title}
-
{product.text}
-
- ))} -
-
-
Tech specs
- - - - - - - - -
-
-
- - {(phones) => ( - Math.random() - 0.5)} - /> - )} - -
- )} - -
- ); -}; diff --git a/src/components/ProductSlider/ProductSlider.scss b/src/components/ProductSlider/ProductSlider.scss index 3fbcf39764..b840036c4d 100644 --- a/src/components/ProductSlider/ProductSlider.scss +++ b/src/components/ProductSlider/ProductSlider.scss @@ -1,10 +1,8 @@ -.product { - &__slider { - max-width: 1136px; - margin: 0 auto; - margin-bottom: 80px; - } +.product-slider { + max-width: 1136px; + margin: 0 auto; + margin-bottom: 80px; &__header { display: flex; diff --git a/src/components/ProductSlider/ProductSlider.tsx b/src/components/ProductSlider/ProductSlider.tsx index 5cfc5c4913..75ff30f030 100644 --- a/src/components/ProductSlider/ProductSlider.tsx +++ b/src/components/ProductSlider/ProductSlider.tsx @@ -30,29 +30,29 @@ export const ProductsSlider = React.memo(({ title, products }: Props) => { }; return ( -
-
-
{title}
-
+
+
+
{title}
+
-
+
) => void, + value: SortOption | ItemsPerPage, + sortBy: string, +}; + +export const SelectTemplate: React.FC = ({ + category, onChange, value, sortBy, +}) => { + if (sortBy === 'age') { + return ( +
+ + +
+ ); + } + + return ( +
+ + +
+ ); +}; diff --git a/src/components/ShopCategory/ShopCategory.tsx b/src/components/ShopCategory/ShopCategory.tsx index 70224cec34..d9a49ca54e 100644 --- a/src/components/ShopCategory/ShopCategory.tsx +++ b/src/components/ShopCategory/ShopCategory.tsx @@ -1,21 +1,19 @@ +import { useMemo } from 'react'; import { Link } from 'react-router-dom'; import phones_banner from '../../images/phones-banner.png'; import tablets_banner from '../../images/tablets-banner.png'; import accessories_banner from '../../images/accessories-banner.png'; -import { Product } from '../../types/Products'; import './ShopCategory.scss'; +import { useAppSelector } from '../../app/hooks'; -type Props = { - products: Product[], -}; - -export const ShopCategory: React.FC = ({ products }) => { - const phonesDescription = [...products] - .filter(item => item.category === 'phones'); - const tabletsDescription = [...products] - .filter(item => item.category === 'tablets'); - const accessoriesDescription = [...products] - .filter(item => item.category === 'accessories'); +export const ShopCategory = () => { + const { products } = useAppSelector(state => state.products); + const phonesDescription = useMemo(() => [...products] + .filter(item => item.category === 'phones'), [products]); + const tabletsDescription = useMemo(() => [...products] + .filter(item => item.category === 'tablets'), [products]); + const accessoriesDescription = useMemo(() => [...products] + .filter(item => item.category === 'accessories'), [products]); return (
) => { + state.productInfo = action.payload; + }, + setCapacity: (state, action: PayloadAction) => { + state.selectedCapacity = action.payload; + }, + setColor: (state, action: PayloadAction) => { + state.selectedColor = action.payload; + }, + }, +}); + +export default productsSlice.reducer; +export const { setProduct, setCapacity, setColor } = productsSlice.actions; diff --git a/src/features/productsSlice.ts b/src/features/productsSlice.ts new file mode 100644 index 0000000000..99be433aa4 --- /dev/null +++ b/src/features/productsSlice.ts @@ -0,0 +1,24 @@ +import { PayloadAction, createSlice } from '@reduxjs/toolkit'; +import { Product } from '../types/Products'; + +type ProductsState = { + products: Product[], +}; + +const InitialState: ProductsState = { + products: [], +}; + +const productsSlice = createSlice({ + name: 'products', + initialState: InitialState, + reducers: { + set: (state, action: PayloadAction) => { + // eslint-disable-next-line no-param-reassign + state.products = action.payload; + }, + }, +}); + +export default productsSlice.reducer; +export const { set } = productsSlice.actions; diff --git a/src/helpers/SearchLink.tsx b/src/helpers/SearchLink.tsx index 2343ec5441..3e81b898f3 100644 --- a/src/helpers/SearchLink.tsx +++ b/src/helpers/SearchLink.tsx @@ -1,34 +1,23 @@ import { Link, LinkProps, useSearchParams } from 'react-router-dom'; import { getSearchWith, SearchParams } from './SearchParams'; -/** - * To replace the the standard `Link` we take all it props except for `to` - * along with the custom `params` prop that we use for updating the search - */ type Props = Omit & { params: SearchParams, }; -/** - * SearchLink updates the given `params` in the search keeping the `pathname` - * and the other existing search params (see `getSearchWith`) - */ export const SearchLink: React.FC = ({ - children, // this is the content between the open and closing tags - params, // the params to be updated in the `search` - ...props // all usual Link props like `className`, `style` and `id` + children, + params, + ...props }) => { const [searchParams] = useSearchParams(); return ( {children} diff --git a/src/helpers/SearchParams.ts b/src/helpers/SearchParams.ts index 42d8db56d5..3395bdac25 100644 --- a/src/helpers/SearchParams.ts +++ b/src/helpers/SearchParams.ts @@ -2,36 +2,19 @@ export type SearchParams = { [key: string]: string | string[] | null, }; -/** - * This function prepares a correct search string - * from a given currentParams and paramsToUpdate. - */ export function getSearchWith( currentParams: URLSearchParams, - paramsToUpdate: SearchParams, // it's our custom type + paramsToUpdate: SearchParams, ): string { - // copy currentParams by creating new object from a string const newParams = new URLSearchParams( currentParams.toString(), ); - // Here is the example of paramsToUpdate - // { - // sex: 'm', ['sex', 'm'] - // order: null, ['order', null] - // centuries: ['16', '19'], ['centuries', ['16', '19']] - // } - // - // - params with the `null` value are deleted; - // - string value is set to given param key; - // - array of strings adds several params with the same key; - Object.entries(paramsToUpdate) .forEach(([key, value]) => { if (value === null) { newParams.delete(key); } else if (Array.isArray(value)) { - // we delete the key to remove old values newParams.delete(key); value.forEach(part => { @@ -42,6 +25,5 @@ export function getSearchWith( } }); - // we return a string to use it inside links return newParams.toString(); } diff --git a/src/helpers/fetchClient.tsx b/src/helpers/fetchClient.tsx new file mode 100644 index 0000000000..4755d06771 --- /dev/null +++ b/src/helpers/fetchClient.tsx @@ -0,0 +1,41 @@ +const BASE_URL = 'https://mate.academy/students-api'; + +function wait(delay: number) { + return new Promise(resolve => { + setTimeout(resolve, delay); + }); +} + +type RequestMethod = 'GET' | 'POST' | 'PATCH' | 'DELETE'; + +function request( + url: string, + method: RequestMethod = 'GET', + data: any = null, +): Promise { + const options: RequestInit = { method }; + + if (data) { + options.body = JSON.stringify(data); + options.headers = { + 'Content-Type': 'application/json; charset=UTF-8', + }; + } + + return wait(300) + .then(() => fetch(BASE_URL + url, options)) + .then(response => { + if (!response.ok) { + throw new Error(); + } + + return response.json(); + }); +} + +export const client = { + get: (url: string) => request(url), + post: (url: string, data: any) => request(url, 'POST', data), + patch: (url: string, data: any) => request(url, 'PATCH', data), + delete: (url: string) => request(url, 'DELETE'), +}; diff --git a/src/helpers/helper.tsx b/src/helpers/helper.tsx index c96869c19a..63a803e2c7 100644 --- a/src/helpers/helper.tsx +++ b/src/helpers/helper.tsx @@ -1,7 +1,6 @@ import { ProductDetails } from '../types/ProductDetails'; import { Product } from '../types/Products'; - -export type SortOption = 'age' | 'price' | 'name'; +import { SortOption } from '../types/SortOption'; export const API_URL = 'https://mate-academy.github.io/react_phone-catalog/_new/products.json'; diff --git a/src/index.tsx b/src/index.tsx index e0d3ee132a..0de886e715 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,10 +1,14 @@ import ReactDOM from 'react-dom'; import { HashRouter } from 'react-router-dom'; +import { Provider } from 'react-redux'; import App from './App'; +import { store } from './app/store'; ReactDOM.render( - - - , + + + + + , document.getElementById('root'), ); diff --git a/src/components/ProductPage/AccessoriesPage/AccessoriesPage.tsx b/src/pages/AccessoriesPage.tsx similarity index 89% rename from src/components/ProductPage/AccessoriesPage/AccessoriesPage.tsx rename to src/pages/AccessoriesPage.tsx index af866b6719..2fa621fea9 100644 --- a/src/components/ProductPage/AccessoriesPage/AccessoriesPage.tsx +++ b/src/pages/AccessoriesPage.tsx @@ -1,19 +1,18 @@ import { - useEffect, useState, useCallback, useMemo, useContext, + useEffect, useState, useCallback, useMemo, } from 'react'; import { useSearchParams } from 'react-router-dom'; import { - updateSearchParams, sortProducts, SortOption, -} from '../../../helpers/helper'; -import { Pagination } from '../../Pagination/Pagination'; -import { Product } from '../../../types/Products'; -import { ProductCard } from '../../ProductCard/ProductCard'; -import { PageIndicator } from '../../PageIndicator/Phonespage/PageIndicator'; -import '../ProductPage.scss'; -import { - ProductDataContext, -} from '../../ProductDataContext/ProductDataContext'; -import { NoResults } from '../../NoResults/NoResults'; + updateSearchParams, sortProducts, +} from '../helpers/helper'; +import { Pagination } from '../components/Pagination/Pagination'; +import { Product } from '../types/Products'; +import { ProductCard } from '../components/ProductCard/ProductCard'; +import { PageIndicator } from '../components/PageIndicator/PageIndicator'; +import './ProductPage.scss'; +import { NoResults } from '../components/NoResults/NoResults'; +import { useAppSelector } from '../app/hooks'; +import { SortOption } from '../types/SortOption'; type ItemsPerPage = '4' | '8' | '16' | 'All'; @@ -24,7 +23,7 @@ export const AccessoriesPage: React.FC = () => { const query = searchParams.get('query'); const currentPageFromParams = Number(searchParams.get('page') || '1'); const [currentPage, setCurrentPage] = useState(currentPageFromParams); - const products = useContext(ProductDataContext); + const { products } = useAppSelector(state => state.products); const handleOnChange = useCallback(( event: React.ChangeEvent, diff --git a/src/pages/Homepage.tsx b/src/pages/Homepage.tsx new file mode 100644 index 0000000000..501042a12c --- /dev/null +++ b/src/pages/Homepage.tsx @@ -0,0 +1,34 @@ +import { useMemo } from 'react'; +import { useAppSelector } from '../app/hooks'; +import { MainSlider } from '../components/MainSlider/MainSlider'; +import { ProductsSlider } from '../components/ProductSlider/ProductSlider'; +import { ShopCategory } from '../components/ShopCategory/ShopCategory'; + +export const Homepage: React.FC = () => { + const { products } = useAppSelector(state => state.products); + const getHotPriceProducts = useMemo(() => { + return [...products] + .sort((a, b) => (b.fullPrice - b.price) - (a.fullPrice - a.price)); + }, [products]); + + const getBrandNewProducts = useMemo(() => { + return [...products].sort((a, b) => b.price - a.price); + }, [products]); + + return ( +
+
+ + + + +
+
+ ); +}; diff --git a/src/components/ProductPage/PhonesPage/PhonesPage.tsx b/src/pages/PhonesPage.tsx similarity index 65% rename from src/components/ProductPage/PhonesPage/PhonesPage.tsx rename to src/pages/PhonesPage.tsx index e563d2a5b1..84381b0275 100644 --- a/src/components/ProductPage/PhonesPage/PhonesPage.tsx +++ b/src/pages/PhonesPage.tsx @@ -1,31 +1,28 @@ import { - useEffect, useState, useCallback, useMemo, useContext, + useEffect, useState, useCallback, useMemo, } from 'react'; import { useSearchParams } from 'react-router-dom'; -import { - updateSearchParams, sortProducts, SortOption, -} from '../../../helpers/helper'; -import { Product } from '../../../types/Products'; -import { ProductCard } from '../../ProductCard/ProductCard'; -import { PageIndicator } from '../../PageIndicator/Phonespage/PageIndicator'; -import '../ProductPage.scss'; -import { - ProductDataContext, -} from '../../ProductDataContext/ProductDataContext'; -import { NoResults } from '../../NoResults/NoResults'; -import { Pagination } from '../../Pagination/Pagination'; - -type ItemsPerPage = '4' | '8' | '16' | 'All'; +import { updateSearchParams, sortProducts } from '../helpers/helper'; +import { useAppSelector } from '../app/hooks'; +import { Product } from '../types/Products'; +import { SortOption } from '../types/SortOption'; +import { ItemsPerPage } from '../types/ItemsPerPage'; +import { ProductCard } from '../components/ProductCard/ProductCard'; +import { PageIndicator } from '../components/PageIndicator/PageIndicator'; +import './ProductPage.scss'; +import { NoResults } from '../components/NoResults/NoResults'; +import { Pagination } from '../components/Pagination/Pagination'; +import { SelectTemplate } from '../components/SelectTemplate/SelectTemplate'; export const PhonesPage: React.FC = () => { const category = 'phones'; + const { products } = useAppSelector(state => state.products); const [searchParams, setSearchParams] = useSearchParams(); const itemsPerPage = searchParams.get('perPage') as ItemsPerPage || '4'; const sortPage = searchParams.get('sort') as SortOption || 'age'; const query = searchParams.get('query'); const currentPageFromParams = Number(searchParams.get('page') || '1'); const [currentPage, setCurrentPage] = useState(currentPageFromParams); - const products = useContext(ProductDataContext); const handleOnChange = useCallback(( event: React.ChangeEvent, @@ -106,35 +103,18 @@ export const PhonesPage: React.FC = () => { {product.length > 0 ? ( <>
-
- - -
-
- - -
+ +
{paginatedProducts?.map((item) => ( diff --git a/src/components/ProductPage/ProductPage.scss b/src/pages/ProductPage.scss similarity index 60% rename from src/components/ProductPage/ProductPage.scss rename to src/pages/ProductPage.scss index 7a9130ae2f..27670d2c7c 100644 --- a/src/components/ProductPage/ProductPage.scss +++ b/src/pages/ProductPage.scss @@ -1,7 +1,6 @@ .phones-page, .tablets-page, .accessories-page { - &__container { max-width: 1136px; margin: 0 auto; @@ -35,34 +34,6 @@ margin-bottom: 24px; } - &__label { - font-size: 12px; - font-weight: 600; - line-height: 15px; - color: rgba(137, 147, 154, 1); - margin-bottom: 4px; - } - - &__sort-by { - display: flex; - flex-direction: column; - } - - &__select { - width: 176px; - min-height: 40px; - padding: 12px 10px; - border: 1px solid #e2e6e9; - font-size: 14px; - font-weight: 600; - line-height: 21px; - } - - &__items-per-page { - display: flex; - flex-direction: column; - } - &__row { display: flex; flex-wrap: wrap; diff --git a/src/components/ProductPage/TabletsPage/TabletsPage.tsx b/src/pages/TabletsPage.tsx similarity index 89% rename from src/components/ProductPage/TabletsPage/TabletsPage.tsx rename to src/pages/TabletsPage.tsx index 37d2ac4b4b..77bd0209e1 100644 --- a/src/components/ProductPage/TabletsPage/TabletsPage.tsx +++ b/src/pages/TabletsPage.tsx @@ -1,19 +1,16 @@ import { - useEffect, useState, useCallback, useMemo, useContext, + useEffect, useState, useCallback, useMemo, } from 'react'; import { useSearchParams } from 'react-router-dom'; -import { - updateSearchParams, sortProducts, SortOption, -} from '../../../helpers/helper'; -import { Pagination } from '../../Pagination/Pagination'; -import { Product } from '../../../types/Products'; -import { ProductCard } from '../../ProductCard/ProductCard'; -import { PageIndicator } from '../../PageIndicator/Phonespage/PageIndicator'; -import '../ProductPage.scss'; -import { - ProductDataContext, -} from '../../ProductDataContext/ProductDataContext'; -import { NoResults } from '../../NoResults/NoResults'; +import { updateSearchParams, sortProducts } from '../helpers/helper'; +import { Pagination } from '../components/Pagination/Pagination'; +import { Product } from '../types/Products'; +import { ProductCard } from '../components/ProductCard/ProductCard'; +import { PageIndicator } from '../components/PageIndicator/PageIndicator'; +import './ProductPage.scss'; +import { NoResults } from '../components/NoResults/NoResults'; +import { useAppSelector } from '../app/hooks'; +import { SortOption } from '../types/SortOption'; type ItemsPerPage = '4' | '8' | '16' | 'All'; @@ -24,7 +21,7 @@ export const TabletsPage: React.FC = () => { const query = searchParams.get('query'); const currentPageFromParams = Number(searchParams.get('page') || '1'); const [currentPage, setCurrentPage] = useState(currentPageFromParams); - const products = useContext(ProductDataContext); + const { products } = useAppSelector(state => state.products); const handleOnChange = useCallback(( event: React.ChangeEvent, diff --git a/src/types/ItemsPerPage.ts b/src/types/ItemsPerPage.ts new file mode 100644 index 0000000000..d35b6f7a33 --- /dev/null +++ b/src/types/ItemsPerPage.ts @@ -0,0 +1 @@ +export type ItemsPerPage = '4' | '8' | '16' | 'All'; diff --git a/src/types/SortOption.ts b/src/types/SortOption.ts new file mode 100644 index 0000000000..30fd529be1 --- /dev/null +++ b/src/types/SortOption.ts @@ -0,0 +1 @@ +export type SortOption = 'age' | 'price' | 'name'; diff --git a/src/types/User.ts b/src/types/User.ts new file mode 100644 index 0000000000..6eafe74c1a --- /dev/null +++ b/src/types/User.ts @@ -0,0 +1,5 @@ +export interface User { + id: number; + name: string; + email: string; +}