From efbb6cfe39add4818f38bddb5faf6448a3baa68e Mon Sep 17 00:00:00 2001 From: Ahmed Khalid <106074266+ahmed-arb@users.noreply.github.com> Date: Thu, 2 Feb 2023 15:56:26 +0500 Subject: [PATCH 1/6] feat: architecture and login --- package-lock.json | 885 +++++++++++++++++++++++++++++++ package.json | 5 + src/App.js | 29 +- src/components/PrivateRoute.jsx | 10 + src/components/Routes.jsx | 12 + src/components/layout/Layout.jsx | 17 + src/index.js | 17 +- src/pages/login/Login.jsx | 93 ++++ 8 files changed, 1043 insertions(+), 25 deletions(-) create mode 100644 src/components/PrivateRoute.jsx create mode 100644 src/components/Routes.jsx create mode 100644 src/components/layout/Layout.jsx create mode 100644 src/pages/login/Login.jsx diff --git a/package-lock.json b/package-lock.json index e03dd4b..d575520 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,11 +8,16 @@ "name": "client", "version": "0.1.0", "dependencies": { + "@emotion/react": "^11.10.5", + "@emotion/styled": "^11.10.5", + "@mui/icons-material": "^5.11.0", + "@mui/material": "^5.11.7", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.8.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" } @@ -2124,6 +2129,170 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.10.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.5.tgz", + "integrity": "sha512-xE7/hyLHJac7D2Ve9dKroBBZqBT7WuPQmWcq7HSGb84sUuP4mlOWoB8dvVfD9yk5DHkU1m6RW7xSoDtnQHNQeA==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/plugin-syntax-jsx": "^7.17.12", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.0", + "@emotion/memoize": "^0.8.0", + "@emotion/serialize": "^1.1.1", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.1.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.10.5", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.5.tgz", + "integrity": "sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==", + "dependencies": { + "@emotion/memoize": "^0.8.0", + "@emotion/sheet": "^1.2.1", + "@emotion/utils": "^1.2.0", + "@emotion/weak-memoize": "^0.3.0", + "stylis": "4.1.3" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz", + "integrity": "sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "dependencies": { + "@emotion/memoize": "^0.8.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + }, + "node_modules/@emotion/react": { + "version": "11.10.5", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.10.5.tgz", + "integrity": "sha512-TZs6235tCJ/7iF6/rvTaOH4oxQg2gMAcdHemjwLKIjKz4rRuYe1HJ2TQJKnAcRAfOUDdU8XoDadCe1rl72iv8A==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.10.5", + "@emotion/cache": "^11.10.5", + "@emotion/serialize": "^1.1.1", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", + "@emotion/utils": "^1.2.0", + "@emotion/weak-memoize": "^0.3.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA==", + "dependencies": { + "@emotion/hash": "^0.9.0", + "@emotion/memoize": "^0.8.0", + "@emotion/unitless": "^0.8.0", + "@emotion/utils": "^1.2.0", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.1.tgz", + "integrity": "sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==" + }, + "node_modules/@emotion/styled": { + "version": "11.10.5", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.10.5.tgz", + "integrity": "sha512-8EP6dD7dMkdku2foLoruPCNkRevzdcBaY6q0l0OsbyJK+x8D9HWjX27ARiSIKNF634hY9Zdoedh8bJCiva8yZw==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.10.5", + "@emotion/is-prop-valid": "^1.2.0", + "@emotion/serialize": "^1.1.1", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", + "@emotion/utils": "^1.2.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", + "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz", + "integrity": "sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.0.tgz", + "integrity": "sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz", + "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==" + }, "node_modules/@eslint/eslintrc": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", @@ -2968,6 +3137,262 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" }, + "node_modules/@mui/base": { + "version": "5.0.0-alpha.116", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.116.tgz", + "integrity": "sha512-VwhifWdrfHc4/ZdqRZ4Gf+7P39sovNN24By1YVZdvJ9fvp0Sr8sNftGUCjYXXz+xCXVBQDXvhfxMwZrj2MvJvA==", + "dependencies": { + "@babel/runtime": "^7.20.7", + "@emotion/is-prop-valid": "^1.2.0", + "@mui/types": "^7.2.3", + "@mui/utils": "^5.11.7", + "@popperjs/core": "^2.11.6", + "clsx": "^1.2.1", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/base/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "5.11.7", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.11.7.tgz", + "integrity": "sha512-lZgX7XQTk0zVcpwEa80r+T4y09dosnUxWvFPSikU/2Hh5wnyNOek8WfJwGCNsaRiXJHMi5eHY+z8oku4u5lgNw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + } + }, + "node_modules/@mui/icons-material": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.11.0.tgz", + "integrity": "sha512-I2LaOKqO8a0xcLGtIozC9xoXjZAto5G5gh0FYUMAlbsIHNHIjn4Xrw9rvjY20vZonyiGrZNMAlAXYkY6JvhF6A==", + "dependencies": { + "@babel/runtime": "^7.20.6" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "5.11.7", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.11.7.tgz", + "integrity": "sha512-wDv7Pc6kMe9jeWkmCLt4JChd1lPc2u23JQHpB35L2VwQowpNFoDfIwqi0sYCnZTMKlRc7lza8LqwSwHl2G52Rw==", + "dependencies": { + "@babel/runtime": "^7.20.7", + "@mui/base": "5.0.0-alpha.116", + "@mui/core-downloads-tracker": "^5.11.7", + "@mui/system": "^5.11.7", + "@mui/types": "^7.2.3", + "@mui/utils": "^5.11.7", + "@types/react-transition-group": "^4.4.5", + "clsx": "^1.2.1", + "csstype": "^3.1.1", + "prop-types": "^15.8.1", + "react-is": "^18.2.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/@mui/private-theming": { + "version": "5.11.7", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.11.7.tgz", + "integrity": "sha512-XzRTSZdc8bhuUdjablTNv3kFkZ/XIMlKkOqqJCU0G8W3tWGXpau2DXkafPd1ddjPhF9zF3qLKNGgKCChYItjgA==", + "dependencies": { + "@babel/runtime": "^7.20.7", + "@mui/utils": "^5.11.7", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.11.0.tgz", + "integrity": "sha512-AF06K60Zc58qf0f7X+Y/QjaHaZq16znliLnGc9iVrV/+s8Ln/FCoeNuFvhlCbZZQ5WQcJvcy59zp0nXrklGGPQ==", + "dependencies": { + "@babel/runtime": "^7.20.6", + "@emotion/cache": "^11.10.5", + "csstype": "^3.1.1", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "5.11.7", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.11.7.tgz", + "integrity": "sha512-uGB6hBxGlAdlmbLdTtUZYNPXkgQGGnKxHdkRATqsu7UlCxNsc/yS5NCEWy/3c4pnelD1LDLD39WrntP9mwhfkQ==", + "dependencies": { + "@babel/runtime": "^7.20.7", + "@mui/private-theming": "^5.11.7", + "@mui/styled-engine": "^5.11.0", + "@mui/types": "^7.2.3", + "@mui/utils": "^5.11.7", + "clsx": "^1.2.1", + "csstype": "^3.1.1", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.3.tgz", + "integrity": "sha512-tZ+CQggbe9Ol7e/Fs5RcKwg/woU+o8DCtOnccX6KmbBc7YrfqMYEYuaIcXHuhpT880QwNkZZ3wQwvtlDFA2yOw==", + "peerDependencies": { + "@types/react": "*" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "5.11.7", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.11.7.tgz", + "integrity": "sha512-8uyNDeVHZA804Ego20Erv8TpxlbqTe/EbhTI2H1UYr4/RiIbBprat8W4Qqr2UQIsC/b3DLz+0RQ6R/E5BxEcLA==", + "dependencies": { + "@babel/runtime": "^7.20.7", + "@types/prop-types": "^15.7.5", + "@types/react-is": "^16.7.1 || ^17.0.0", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0" + } + }, + "node_modules/@mui/utils/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -3077,6 +3502,23 @@ } } }, + "node_modules/@popperjs/core": { + "version": "2.11.6", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", + "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@remix-run/router": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.1.tgz", + "integrity": "sha512-+eun1Wtf72RNRSqgU7qM2AMX/oHp+dnx7BHk1qhK5ZHzdHTUU4LA1mGG1vT+jMc8sbhG3orvsfOmryjzx2PzQw==", + "engines": { + "node": ">=14" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -4073,6 +4515,22 @@ "@types/react": "*" } }, + "node_modules/@types/react-is": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz", + "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -5642,6 +6100,14 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -6570,6 +7036,15 @@ "utila": "~0.4" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -7967,6 +8442,11 @@ "url": "https://github.com/avajs/find-cache-dir?sponsor=1" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -8595,6 +9075,19 @@ "he": "bin/he" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/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/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -14227,6 +14720,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.0.tgz", + "integrity": "sha512-760bk7y3QwabduExtudhWbd88IBbuD1YfwzpuDUAlJUJ7laIIcqhMvdhSVh1Fur1PE8cGl84L0dxhR3/gvHF7A==", + "dependencies": { + "@remix-run/router": "1.3.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.0.tgz", + "integrity": "sha512-hQouduSTywGJndE86CXJ2h7YEy4HYC6C/uh19etM+79FfQ6cFFFHnHyDlzO4Pq0eBUI96E4qVE5yUjA00yJZGQ==", + "dependencies": { + "@remix-run/router": "1.3.1", + "react-router": "6.8.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -14299,6 +14822,21 @@ } } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -15422,6 +15960,11 @@ "postcss": "^8.2.15" } }, + "node_modules/stylis": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz", + "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==" + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -18463,6 +19006,133 @@ "integrity": "sha512-jwx+WCqszn53YHOfvFMJJRd/B2GqkCBt+1MJSG6o5/s8+ytHMvDZXsJgUEWLk12UnLd7HYKac4BYU5i/Ron1Cw==", "requires": {} }, + "@emotion/babel-plugin": { + "version": "11.10.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.5.tgz", + "integrity": "sha512-xE7/hyLHJac7D2Ve9dKroBBZqBT7WuPQmWcq7HSGb84sUuP4mlOWoB8dvVfD9yk5DHkU1m6RW7xSoDtnQHNQeA==", + "requires": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/plugin-syntax-jsx": "^7.17.12", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.0", + "@emotion/memoize": "^0.8.0", + "@emotion/serialize": "^1.1.1", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.1.3" + }, + "dependencies": { + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" + } + } + }, + "@emotion/cache": { + "version": "11.10.5", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.5.tgz", + "integrity": "sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==", + "requires": { + "@emotion/memoize": "^0.8.0", + "@emotion/sheet": "^1.2.1", + "@emotion/utils": "^1.2.0", + "@emotion/weak-memoize": "^0.3.0", + "stylis": "4.1.3" + } + }, + "@emotion/hash": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz", + "integrity": "sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==" + }, + "@emotion/is-prop-valid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "requires": { + "@emotion/memoize": "^0.8.0" + } + }, + "@emotion/memoize": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + }, + "@emotion/react": { + "version": "11.10.5", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.10.5.tgz", + "integrity": "sha512-TZs6235tCJ/7iF6/rvTaOH4oxQg2gMAcdHemjwLKIjKz4rRuYe1HJ2TQJKnAcRAfOUDdU8XoDadCe1rl72iv8A==", + "requires": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.10.5", + "@emotion/cache": "^11.10.5", + "@emotion/serialize": "^1.1.1", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", + "@emotion/utils": "^1.2.0", + "@emotion/weak-memoize": "^0.3.0", + "hoist-non-react-statics": "^3.3.1" + } + }, + "@emotion/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA==", + "requires": { + "@emotion/hash": "^0.9.0", + "@emotion/memoize": "^0.8.0", + "@emotion/unitless": "^0.8.0", + "@emotion/utils": "^1.2.0", + "csstype": "^3.0.2" + } + }, + "@emotion/sheet": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.1.tgz", + "integrity": "sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==" + }, + "@emotion/styled": { + "version": "11.10.5", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.10.5.tgz", + "integrity": "sha512-8EP6dD7dMkdku2foLoruPCNkRevzdcBaY6q0l0OsbyJK+x8D9HWjX27ARiSIKNF634hY9Zdoedh8bJCiva8yZw==", + "requires": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.10.5", + "@emotion/is-prop-valid": "^1.2.0", + "@emotion/serialize": "^1.1.1", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", + "@emotion/utils": "^1.2.0" + } + }, + "@emotion/unitless": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", + "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" + }, + "@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz", + "integrity": "sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==", + "requires": {} + }, + "@emotion/utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.0.tgz", + "integrity": "sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==" + }, + "@emotion/weak-memoize": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz", + "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==" + }, "@eslint/eslintrc": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", @@ -19088,6 +19758,128 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" }, + "@mui/base": { + "version": "5.0.0-alpha.116", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.116.tgz", + "integrity": "sha512-VwhifWdrfHc4/ZdqRZ4Gf+7P39sovNN24By1YVZdvJ9fvp0Sr8sNftGUCjYXXz+xCXVBQDXvhfxMwZrj2MvJvA==", + "requires": { + "@babel/runtime": "^7.20.7", + "@emotion/is-prop-valid": "^1.2.0", + "@mui/types": "^7.2.3", + "@mui/utils": "^5.11.7", + "@popperjs/core": "^2.11.6", + "clsx": "^1.2.1", + "prop-types": "^15.8.1", + "react-is": "^18.2.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==" + } + } + }, + "@mui/core-downloads-tracker": { + "version": "5.11.7", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.11.7.tgz", + "integrity": "sha512-lZgX7XQTk0zVcpwEa80r+T4y09dosnUxWvFPSikU/2Hh5wnyNOek8WfJwGCNsaRiXJHMi5eHY+z8oku4u5lgNw==" + }, + "@mui/icons-material": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.11.0.tgz", + "integrity": "sha512-I2LaOKqO8a0xcLGtIozC9xoXjZAto5G5gh0FYUMAlbsIHNHIjn4Xrw9rvjY20vZonyiGrZNMAlAXYkY6JvhF6A==", + "requires": { + "@babel/runtime": "^7.20.6" + } + }, + "@mui/material": { + "version": "5.11.7", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.11.7.tgz", + "integrity": "sha512-wDv7Pc6kMe9jeWkmCLt4JChd1lPc2u23JQHpB35L2VwQowpNFoDfIwqi0sYCnZTMKlRc7lza8LqwSwHl2G52Rw==", + "requires": { + "@babel/runtime": "^7.20.7", + "@mui/base": "5.0.0-alpha.116", + "@mui/core-downloads-tracker": "^5.11.7", + "@mui/system": "^5.11.7", + "@mui/types": "^7.2.3", + "@mui/utils": "^5.11.7", + "@types/react-transition-group": "^4.4.5", + "clsx": "^1.2.1", + "csstype": "^3.1.1", + "prop-types": "^15.8.1", + "react-is": "^18.2.0", + "react-transition-group": "^4.4.5" + }, + "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==" + } + } + }, + "@mui/private-theming": { + "version": "5.11.7", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.11.7.tgz", + "integrity": "sha512-XzRTSZdc8bhuUdjablTNv3kFkZ/XIMlKkOqqJCU0G8W3tWGXpau2DXkafPd1ddjPhF9zF3qLKNGgKCChYItjgA==", + "requires": { + "@babel/runtime": "^7.20.7", + "@mui/utils": "^5.11.7", + "prop-types": "^15.8.1" + } + }, + "@mui/styled-engine": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.11.0.tgz", + "integrity": "sha512-AF06K60Zc58qf0f7X+Y/QjaHaZq16znliLnGc9iVrV/+s8Ln/FCoeNuFvhlCbZZQ5WQcJvcy59zp0nXrklGGPQ==", + "requires": { + "@babel/runtime": "^7.20.6", + "@emotion/cache": "^11.10.5", + "csstype": "^3.1.1", + "prop-types": "^15.8.1" + } + }, + "@mui/system": { + "version": "5.11.7", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.11.7.tgz", + "integrity": "sha512-uGB6hBxGlAdlmbLdTtUZYNPXkgQGGnKxHdkRATqsu7UlCxNsc/yS5NCEWy/3c4pnelD1LDLD39WrntP9mwhfkQ==", + "requires": { + "@babel/runtime": "^7.20.7", + "@mui/private-theming": "^5.11.7", + "@mui/styled-engine": "^5.11.0", + "@mui/types": "^7.2.3", + "@mui/utils": "^5.11.7", + "clsx": "^1.2.1", + "csstype": "^3.1.1", + "prop-types": "^15.8.1" + } + }, + "@mui/types": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.3.tgz", + "integrity": "sha512-tZ+CQggbe9Ol7e/Fs5RcKwg/woU+o8DCtOnccX6KmbBc7YrfqMYEYuaIcXHuhpT880QwNkZZ3wQwvtlDFA2yOw==", + "requires": {} + }, + "@mui/utils": { + "version": "5.11.7", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.11.7.tgz", + "integrity": "sha512-8uyNDeVHZA804Ego20Erv8TpxlbqTe/EbhTI2H1UYr4/RiIbBprat8W4Qqr2UQIsC/b3DLz+0RQ6R/E5BxEcLA==", + "requires": { + "@babel/runtime": "^7.20.7", + "@types/prop-types": "^15.7.5", + "@types/react-is": "^16.7.1 || ^17.0.0", + "prop-types": "^15.8.1", + "react-is": "^18.2.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==" + } + } + }, "@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -19151,6 +19943,16 @@ "source-map": "^0.7.3" } }, + "@popperjs/core": { + "version": "2.11.6", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", + "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==" + }, + "@remix-run/router": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.1.tgz", + "integrity": "sha512-+eun1Wtf72RNRSqgU7qM2AMX/oHp+dnx7BHk1qhK5ZHzdHTUU4LA1mGG1vT+jMc8sbhG3orvsfOmryjzx2PzQw==" + }, "@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -19907,6 +20709,22 @@ "@types/react": "*" } }, + "@types/react-is": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz", + "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==", + "requires": { + "@types/react": "*" + } + }, + "@types/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", + "requires": { + "@types/react": "*" + } + }, "@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -21070,6 +21888,11 @@ "wrap-ansi": "^7.0.0" } }, + "clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==" + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -21743,6 +22566,15 @@ "utila": "~0.4" } }, + "dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -22789,6 +23621,11 @@ "pkg-dir": "^4.1.0" } }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -23209,6 +24046,21 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + }, + "dependencies": { + "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==" + } + } + }, "hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -27093,6 +27945,23 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" }, + "react-router": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.0.tgz", + "integrity": "sha512-760bk7y3QwabduExtudhWbd88IBbuD1YfwzpuDUAlJUJ7laIIcqhMvdhSVh1Fur1PE8cGl84L0dxhR3/gvHF7A==", + "requires": { + "@remix-run/router": "1.3.1" + } + }, + "react-router-dom": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.0.tgz", + "integrity": "sha512-hQouduSTywGJndE86CXJ2h7YEy4HYC6C/uh19etM+79FfQ6cFFFHnHyDlzO4Pq0eBUI96E4qVE5yUjA00yJZGQ==", + "requires": { + "@remix-run/router": "1.3.1", + "react-router": "6.8.0" + } + }, "react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -27148,6 +28017,17 @@ "workbox-webpack-plugin": "^6.4.1" } }, + "react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "requires": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + } + }, "read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -27970,6 +28850,11 @@ "postcss-selector-parser": "^6.0.4" } }, + "stylis": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz", + "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==" + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", diff --git a/package.json b/package.json index 866d594..4a00bd2 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,16 @@ "version": "0.1.0", "private": true, "dependencies": { + "@emotion/react": "^11.10.5", + "@emotion/styled": "^11.10.5", + "@mui/icons-material": "^5.11.0", + "@mui/material": "^5.11.7", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.8.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, diff --git a/src/App.js b/src/App.js index 3784575..1d7608a 100644 --- a/src/App.js +++ b/src/App.js @@ -1,24 +1,17 @@ -import logo from './logo.svg'; -import './App.css'; +import { Route, Routes } from "react-router-dom"; +import PrivateRoute from "./components/PrivateRoute"; + +import Layout from "./components/layout/Layout"; +import Login from "./pages/login/Login"; function App() { return ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
-
+ + } /> + }> + } /> + + ); } diff --git a/src/components/PrivateRoute.jsx b/src/components/PrivateRoute.jsx new file mode 100644 index 0000000..f3af732 --- /dev/null +++ b/src/components/PrivateRoute.jsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { Navigate, Outlet } from 'react-router-dom'; + +const PrivateRoute = () => { + const auth = null; // TODO: determine if authorized, from redux later. + + return auth ? : ; +} + +export default PrivateRoute; diff --git a/src/components/Routes.jsx b/src/components/Routes.jsx new file mode 100644 index 0000000..ac22b2b --- /dev/null +++ b/src/components/Routes.jsx @@ -0,0 +1,12 @@ +import React from "react"; +import { Route, Routes, Navigate } from "react-router-dom"; + +const AppRoutes = () => { + return ( + + } /> + + ); +}; + +export default AppRoutes; diff --git a/src/components/layout/Layout.jsx b/src/components/layout/Layout.jsx new file mode 100644 index 0000000..672e84e --- /dev/null +++ b/src/components/layout/Layout.jsx @@ -0,0 +1,17 @@ +import React from "react"; +import AppRoutes from "../Routes"; +import { Route } from "react-router-dom"; + +const Layout = () => { + return ( + ( +
+ +
+ )} + /> + ); +}; + +export default Layout; diff --git a/src/index.js b/src/index.js index d563c0f..b390529 100644 --- a/src/index.js +++ b/src/index.js @@ -1,13 +1,16 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import App from './App'; -import reportWebVitals from './reportWebVitals'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import "./index.css"; +import App from "./App"; +import reportWebVitals from "./reportWebVitals"; +import { BrowserRouter } from "react-router-dom"; -const root = ReactDOM.createRoot(document.getElementById('root')); +const root = ReactDOM.createRoot(document.getElementById("root")); root.render( - + + + ); diff --git a/src/pages/login/Login.jsx b/src/pages/login/Login.jsx new file mode 100644 index 0000000..8de0fab --- /dev/null +++ b/src/pages/login/Login.jsx @@ -0,0 +1,93 @@ +import * as React from "react"; +import Avatar from "@mui/material/Avatar"; +import Button from "@mui/material/Button"; +import CssBaseline from "@mui/material/CssBaseline"; +import TextField from "@mui/material/TextField"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import Checkbox from "@mui/material/Checkbox"; +import Link from "@mui/material/Link"; +import Grid from "@mui/material/Grid"; +import Box from "@mui/material/Box"; +import LockOutlinedIcon from "@mui/icons-material/LockOutlined"; +import Typography from "@mui/material/Typography"; +import Container from "@mui/material/Container"; +import { createTheme, ThemeProvider } from "@mui/material/styles"; + +const theme = createTheme(); + +export default function SignIn() { + const handleSubmit = (event) => { + event.preventDefault(); + const data = new FormData(event.currentTarget); + console.log({ + email: data.get("email"), + password: data.get("password"), + }); + }; + + return ( + + + + + + + + + Sign in + + + + + + + + + + {"Don't have an account? Sign Up"} + + + + + + + + ); +} From b7100f6cfe46a516a010c4401f497a7e67aa56c4 Mon Sep 17 00:00:00 2001 From: Ahmed Khalid <106074266+ahmed-arb@users.noreply.github.com> Date: Tue, 7 Feb 2023 17:20:12 +0500 Subject: [PATCH 2/6] add navbar --- src/App.js | 18 ++-- src/components/navbar/Navbar.jsx | 174 +++++++++++++++++++++++++++++++ src/pages/home/Home.jsx | 9 ++ src/pages/login/Login.jsx | 19 ++-- src/pages/register/Register.jsx | 130 +++++++++++++++++++++++ 5 files changed, 332 insertions(+), 18 deletions(-) create mode 100644 src/components/navbar/Navbar.jsx create mode 100644 src/pages/home/Home.jsx create mode 100644 src/pages/register/Register.jsx diff --git a/src/App.js b/src/App.js index 1d7608a..c7f04f6 100644 --- a/src/App.js +++ b/src/App.js @@ -3,15 +3,21 @@ import PrivateRoute from "./components/PrivateRoute"; import Layout from "./components/layout/Layout"; import Login from "./pages/login/Login"; +import Register from "./pages/register/Register"; +import Navbar from "./components/navbar/Navbar"; function App() { return ( - - } /> - }> - } /> - - + <> + + + } /> + } /> + }> + } /> + + + ); } diff --git a/src/components/navbar/Navbar.jsx b/src/components/navbar/Navbar.jsx new file mode 100644 index 0000000..22e7d79 --- /dev/null +++ b/src/components/navbar/Navbar.jsx @@ -0,0 +1,174 @@ +import React, { useEffect, useState } from "react"; +import AppBar from "@mui/material/AppBar"; +import Box from "@mui/material/Box"; +import Toolbar from "@mui/material/Toolbar"; +import IconButton from "@mui/material/IconButton"; +import Typography from "@mui/material/Typography"; +import Menu from "@mui/material/Menu"; +import MenuIcon from "@mui/icons-material/Menu"; +import Container from "@mui/material/Container"; +import Avatar from "@mui/material/Avatar"; +import Button from "@mui/material/Button"; +import Tooltip from "@mui/material/Tooltip"; +import MenuItem from "@mui/material/MenuItem"; +import AdbIcon from "@mui/icons-material/Adb"; + +const userPages = ["Books", "My Books", "My Requests"]; +const librarianPages = ["Books", "Books Loans", "Book Requests"]; +const anonPages = ["Books"]; +const settings = ["Profile", "Account", "Dashboard", "Logout"]; + +function ResponsiveAppBar() { + const [anchorElNav, setAnchorElNav] = useState(null); + const [anchorElUser, setAnchorElUser] = useState(null); + const [pages, setPages] = useState(anonPages); + const [user] = useState({ isAuth: false, isLibrarian: false }); + + useEffect(() => { + if (user.isLibrarian) { + setPages(librarianPages); + } else if (user.isAuth) { + setPages(userPages); + } + }, [user]); + + const handleOpenNavMenu = (event) => { + setAnchorElNav(event.currentTarget); + }; + const handleOpenUserMenu = (event) => { + setAnchorElUser(event.currentTarget); + }; + + const handleCloseNavMenu = () => { + setAnchorElNav(null); + }; + + const handleCloseUserMenu = () => { + setAnchorElUser(null); + }; + + return ( + + + + + + LOGO + + + + + + + + {pages.map((page) => ( + + {page} + + ))} + + + + + LMS + + + {pages.map((page) => ( + + ))} + + + + + + + + + + {settings.map((setting) => ( + + {setting} + + ))} + + + + + + ); +} +export default ResponsiveAppBar; diff --git a/src/pages/home/Home.jsx b/src/pages/home/Home.jsx new file mode 100644 index 0000000..96c5535 --- /dev/null +++ b/src/pages/home/Home.jsx @@ -0,0 +1,9 @@ +import React from 'react' + +const Home = () => { + return ( +
Home
+ ) +} + +export default Home \ No newline at end of file diff --git a/src/pages/login/Login.jsx b/src/pages/login/Login.jsx index 8de0fab..63b98c6 100644 --- a/src/pages/login/Login.jsx +++ b/src/pages/login/Login.jsx @@ -1,19 +1,16 @@ -import * as React from "react"; +import React from "react"; import Avatar from "@mui/material/Avatar"; import Button from "@mui/material/Button"; import CssBaseline from "@mui/material/CssBaseline"; import TextField from "@mui/material/TextField"; -import FormControlLabel from "@mui/material/FormControlLabel"; -import Checkbox from "@mui/material/Checkbox"; import Link from "@mui/material/Link"; import Grid from "@mui/material/Grid"; import Box from "@mui/material/Box"; import LockOutlinedIcon from "@mui/icons-material/LockOutlined"; import Typography from "@mui/material/Typography"; import Container from "@mui/material/Container"; -import { createTheme, ThemeProvider } from "@mui/material/styles"; -const theme = createTheme(); + export default function SignIn() { const handleSubmit = (event) => { @@ -26,7 +23,6 @@ export default function SignIn() { }; return ( - - + {"Don't have an account? Sign Up"} @@ -88,6 +84,5 @@ export default function SignIn() { - ); } diff --git a/src/pages/register/Register.jsx b/src/pages/register/Register.jsx new file mode 100644 index 0000000..e86d4f3 --- /dev/null +++ b/src/pages/register/Register.jsx @@ -0,0 +1,130 @@ +import React from "react"; +import Avatar from "@mui/material/Avatar"; +import Button from "@mui/material/Button"; +import CssBaseline from "@mui/material/CssBaseline"; +import TextField from "@mui/material/TextField"; + +import Link from "@mui/material/Link"; +import Grid from "@mui/material/Grid"; +import Box from "@mui/material/Box"; +import LockOutlinedIcon from "@mui/icons-material/LockOutlined"; +import Typography from "@mui/material/Typography"; +import Container from "@mui/material/Container"; +import Select from "@mui/material/Select"; +import MenuItem from "@mui/material/MenuItem"; +import FormControl from "@mui/material/FormControl"; +import InputLabel from "@mui/material/InputLabel"; + +export default function SignUp() { + const [gender, setGender] = React.useState(""); + + const handleChange = (event) => { + setGender(event.target.value); + }; + const handleSubmit = (event) => { + event.preventDefault(); + const data = new FormData(event.currentTarget); + console.log({ + email: data.get("email"), + password: data.get("password"), + }); + }; + + return ( + + + + + + + + Sign up + + + + + + + + + + + + + + + + + + Gender + + + + + + + + + Already have an account? Sign in + + + + + + + ); +} From 886754f47305fca01155e0fd1c072e43c2025309 Mon Sep 17 00:00:00 2001 From: Ahmed Khalid <106074266+ahmed-arb@users.noreply.github.com> Date: Wed, 8 Feb 2023 18:47:39 +0500 Subject: [PATCH 3/6] setup user redux --- .env | 1 + package-lock.json | 406 +++++++++++++++++++++++++-- package.json | 5 + src/App.css | 38 --- src/App.js | 64 ++++- src/components/PrivateRoute.jsx | 11 +- src/components/Routes.jsx | 12 - src/components/layout/Layout.jsx | 24 +- src/components/navbar/Navbar.jsx | 185 ++++-------- src/index.js | 10 +- src/interceptor/interceptor.js | 28 ++ src/pages/404/PageNotFound.jsx | 9 + src/pages/login/Login.jsx | 151 +++++----- src/pages/my_books/MyBooks.jsx | 9 + src/pages/my_requests/MyRequests.jsx | 9 + src/store/Store.js | 8 + src/store/actions/userActions.jsx | 24 ++ src/store/slices/userSlice.jsx | 51 ++++ 18 files changed, 748 insertions(+), 297 deletions(-) create mode 100644 .env delete mode 100644 src/App.css delete mode 100644 src/components/Routes.jsx create mode 100644 src/interceptor/interceptor.js create mode 100644 src/pages/404/PageNotFound.jsx create mode 100644 src/pages/my_books/MyBooks.jsx create mode 100644 src/pages/my_requests/MyRequests.jsx create mode 100644 src/store/Store.js create mode 100644 src/store/actions/userActions.jsx create mode 100644 src/store/slices/userSlice.jsx diff --git a/.env b/.env new file mode 100644 index 0000000..29b0abf --- /dev/null +++ b/.env @@ -0,0 +1 @@ +REACT_APP_BACKEND_URL=http://localhost:8000 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d575520..298c884 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,14 +11,19 @@ "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", "@mui/icons-material": "^5.11.0", + "@mui/lab": "^5.0.0-alpha.119", "@mui/material": "^5.11.7", + "@reduxjs/toolkit": "^1.9.2", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.3.2", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-redux": "^8.0.5", "react-router-dom": "^6.8.0", "react-scripts": "5.0.1", + "react-toastify": "^9.1.1", "web-vitals": "^2.1.4" } }, @@ -3208,6 +3213,84 @@ } } }, + "node_modules/@mui/lab": { + "version": "5.0.0-alpha.119", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.119.tgz", + "integrity": "sha512-74l+gA7fybcB2zyOLjWsPbOxt2F4ydu4t0a5rEC/Y4v6q0wDKaW1El1S/+zuE5Xw/fQmiwNrQ+Do5fDbRZDAsw==", + "dependencies": { + "@babel/runtime": "^7.20.7", + "@mui/base": "5.0.0-alpha.117", + "@mui/system": "^5.11.8", + "@mui/types": "^7.2.3", + "@mui/utils": "^5.11.7", + "clsx": "^1.2.1", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/lab/node_modules/@mui/base": { + "version": "5.0.0-alpha.117", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.117.tgz", + "integrity": "sha512-3GlRSZdSrvDQ4k03dSV2rM+97JbNWimFOqGsE7n7Mi8WuBSYCgnPe56bQp3E5cShOrTn11dGH8FRCmVMcCEXqQ==", + "dependencies": { + "@babel/runtime": "^7.20.7", + "@emotion/is-prop-valid": "^1.2.0", + "@mui/types": "^7.2.3", + "@mui/utils": "^5.11.7", + "@popperjs/core": "^2.11.6", + "clsx": "^1.2.1", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/lab/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, "node_modules/@mui/material": { "version": "5.11.7", "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.11.7.tgz", @@ -3284,11 +3367,11 @@ } }, "node_modules/@mui/styled-engine": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.11.0.tgz", - "integrity": "sha512-AF06K60Zc58qf0f7X+Y/QjaHaZq16znliLnGc9iVrV/+s8Ln/FCoeNuFvhlCbZZQ5WQcJvcy59zp0nXrklGGPQ==", + "version": "5.11.8", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.11.8.tgz", + "integrity": "sha512-iSpZp9AoeictsDi5xAQ4PGXu7mKtQyzMl7ZaWpHIGMFpsNnfY3NQNg+wkj/gpsAZ+Zg+IIyD+t+ig71Kr9fa0w==", "dependencies": { - "@babel/runtime": "^7.20.6", + "@babel/runtime": "^7.20.7", "@emotion/cache": "^11.10.5", "csstype": "^3.1.1", "prop-types": "^15.8.1" @@ -3315,13 +3398,13 @@ } }, "node_modules/@mui/system": { - "version": "5.11.7", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.11.7.tgz", - "integrity": "sha512-uGB6hBxGlAdlmbLdTtUZYNPXkgQGGnKxHdkRATqsu7UlCxNsc/yS5NCEWy/3c4pnelD1LDLD39WrntP9mwhfkQ==", + "version": "5.11.8", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.11.8.tgz", + "integrity": "sha512-zhroUcxAw2x/dISBJKhGbD70DOYCwMFRo7o/LUYTiUfQkfmLhRfEf1bopWgY9nYstn7QOxOq9fA3aR3pHrUTbw==", "dependencies": { "@babel/runtime": "^7.20.7", "@mui/private-theming": "^5.11.7", - "@mui/styled-engine": "^5.11.0", + "@mui/styled-engine": "^5.11.8", "@mui/types": "^7.2.3", "@mui/utils": "^5.11.7", "clsx": "^1.2.1", @@ -3511,6 +3594,29 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@reduxjs/toolkit": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.2.tgz", + "integrity": "sha512-5ZAZ7hwAKWSii5T6NTPmgIBUqyVdlDs+6JjThz6J6dmHLDm6zCzv2OjHIFAi3Vvs1qjmXU0bm6eBojukYXjVMQ==", + "dependencies": { + "immer": "^9.0.16", + "redux": "^4.2.0", + "redux-thunk": "^2.4.2", + "reselect": "^4.1.7" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.0.2" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@remix-run/router": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.1.tgz", @@ -4177,6 +4283,15 @@ "@types/node": "*" } }, + "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", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -4597,6 +4712,11 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" }, + "node_modules/@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==" + }, "node_modules/@types/ws": { "version": "8.5.4", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", @@ -5411,6 +5531,29 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.2.tgz", + "integrity": "sha512-1M3O703bYqYuPhbHeya5bnhpYVsDDRyQSabNja04mZtboLNSuZ4YrltestrLXfHgmzua4TpUqRiVKbiQuo2epw==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/axobject-query": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", @@ -14418,6 +14561,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -14712,6 +14860,49 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/react-redux": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", + "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==", + "dependencies": { + "@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" + }, + "peerDependencies": { + "@types/react": "^16.8 || ^17.0 || ^18.0", + "@types/react-dom": "^16.8 || ^17.0 || ^18.0", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0", + "react-native": ">=0.59", + "redux": "^4" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-redux/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -14822,6 +15013,18 @@ } } }, + "node_modules/react-toastify": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.1.tgz", + "integrity": "sha512-pkFCla1z3ve045qvjEmn2xOJOy4ZciwRXm1oMPULVkELi5aJdHCN/FHnuqXq8IwGDLB7PPk2/J6uP9D8ejuiRw==", + "dependencies": { + "clsx": "^1.1.1" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -14892,6 +15095,22 @@ "node": ">=8" } }, + "node_modules/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==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, + "node_modules/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==", + "peerDependencies": { + "redux": "^4" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -15034,6 +15253,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, + "node_modules/reselect": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz", + "integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==" + }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -16628,6 +16852,14 @@ "requires-port": "^1.0.0" } }, + "node_modules/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==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -19793,6 +20025,43 @@ "@babel/runtime": "^7.20.6" } }, + "@mui/lab": { + "version": "5.0.0-alpha.119", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.119.tgz", + "integrity": "sha512-74l+gA7fybcB2zyOLjWsPbOxt2F4ydu4t0a5rEC/Y4v6q0wDKaW1El1S/+zuE5Xw/fQmiwNrQ+Do5fDbRZDAsw==", + "requires": { + "@babel/runtime": "^7.20.7", + "@mui/base": "5.0.0-alpha.117", + "@mui/system": "^5.11.8", + "@mui/types": "^7.2.3", + "@mui/utils": "^5.11.7", + "clsx": "^1.2.1", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + }, + "dependencies": { + "@mui/base": { + "version": "5.0.0-alpha.117", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.117.tgz", + "integrity": "sha512-3GlRSZdSrvDQ4k03dSV2rM+97JbNWimFOqGsE7n7Mi8WuBSYCgnPe56bQp3E5cShOrTn11dGH8FRCmVMcCEXqQ==", + "requires": { + "@babel/runtime": "^7.20.7", + "@emotion/is-prop-valid": "^1.2.0", + "@mui/types": "^7.2.3", + "@mui/utils": "^5.11.7", + "@popperjs/core": "^2.11.6", + "clsx": "^1.2.1", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + } + } + }, "@mui/material": { "version": "5.11.7", "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.11.7.tgz", @@ -19830,24 +20099,24 @@ } }, "@mui/styled-engine": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.11.0.tgz", - "integrity": "sha512-AF06K60Zc58qf0f7X+Y/QjaHaZq16znliLnGc9iVrV/+s8Ln/FCoeNuFvhlCbZZQ5WQcJvcy59zp0nXrklGGPQ==", + "version": "5.11.8", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.11.8.tgz", + "integrity": "sha512-iSpZp9AoeictsDi5xAQ4PGXu7mKtQyzMl7ZaWpHIGMFpsNnfY3NQNg+wkj/gpsAZ+Zg+IIyD+t+ig71Kr9fa0w==", "requires": { - "@babel/runtime": "^7.20.6", + "@babel/runtime": "^7.20.7", "@emotion/cache": "^11.10.5", "csstype": "^3.1.1", "prop-types": "^15.8.1" } }, "@mui/system": { - "version": "5.11.7", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.11.7.tgz", - "integrity": "sha512-uGB6hBxGlAdlmbLdTtUZYNPXkgQGGnKxHdkRATqsu7UlCxNsc/yS5NCEWy/3c4pnelD1LDLD39WrntP9mwhfkQ==", + "version": "5.11.8", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.11.8.tgz", + "integrity": "sha512-zhroUcxAw2x/dISBJKhGbD70DOYCwMFRo7o/LUYTiUfQkfmLhRfEf1bopWgY9nYstn7QOxOq9fA3aR3pHrUTbw==", "requires": { "@babel/runtime": "^7.20.7", "@mui/private-theming": "^5.11.7", - "@mui/styled-engine": "^5.11.0", + "@mui/styled-engine": "^5.11.8", "@mui/types": "^7.2.3", "@mui/utils": "^5.11.7", "clsx": "^1.2.1", @@ -19948,6 +20217,17 @@ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==" }, + "@reduxjs/toolkit": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.2.tgz", + "integrity": "sha512-5ZAZ7hwAKWSii5T6NTPmgIBUqyVdlDs+6JjThz6J6dmHLDm6zCzv2OjHIFAi3Vvs1qjmXU0bm6eBojukYXjVMQ==", + "requires": { + "immer": "^9.0.16", + "redux": "^4.2.0", + "redux-thunk": "^2.4.2", + "reselect": "^4.1.7" + } + }, "@remix-run/router": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.1.tgz", @@ -20424,6 +20704,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": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -20791,6 +21080,11 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" }, + "@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/ws": { "version": "8.5.4", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", @@ -21374,6 +21668,28 @@ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.6.3.tgz", "integrity": "sha512-/BQzOX780JhsxDnPpH4ZiyrJAzcd8AfzFPkv+89veFSr1rcMjuq2JDCwypKaPeB6ljHp9KjXhPpjgCvQlWYuqg==" }, + "axios": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.2.tgz", + "integrity": "sha512-1M3O703bYqYuPhbHeya5bnhpYVsDDRyQSabNja04mZtboLNSuZ4YrltestrLXfHgmzua4TpUqRiVKbiQuo2epw==", + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + }, + "dependencies": { + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "axobject-query": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", @@ -27729,6 +28045,11 @@ } } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -27940,6 +28261,26 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "react-redux": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", + "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==", + "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.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -28017,6 +28358,14 @@ "workbox-webpack-plugin": "^6.4.1" } }, + "react-toastify": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.1.tgz", + "integrity": "sha512-pkFCla1z3ve045qvjEmn2xOJOy4ZciwRXm1oMPULVkELi5aJdHCN/FHnuqXq8IwGDLB7PPk2/J6uP9D8ejuiRw==", + "requires": { + "clsx": "^1.1.1" + } + }, "react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -28071,6 +28420,20 @@ "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==", + "requires": {} + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -28182,6 +28545,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, + "reselect": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz", + "integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==" + }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -29342,6 +29710,12 @@ "requires-port": "^1.0.0" } }, + "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==", + "requires": {} + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 4a00bd2..625565a 100644 --- a/package.json +++ b/package.json @@ -6,14 +6,19 @@ "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", "@mui/icons-material": "^5.11.0", + "@mui/lab": "^5.0.0-alpha.119", "@mui/material": "^5.11.7", + "@reduxjs/toolkit": "^1.9.2", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.3.2", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-redux": "^8.0.5", "react-router-dom": "^6.8.0", "react-scripts": "5.0.1", + "react-toastify": "^9.1.1", "web-vitals": "^2.1.4" }, "scripts": { diff --git a/src/App.css b/src/App.css deleted file mode 100644 index 74b5e05..0000000 --- a/src/App.css +++ /dev/null @@ -1,38 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/src/App.js b/src/App.js index c7f04f6..287c94b 100644 --- a/src/App.js +++ b/src/App.js @@ -1,23 +1,69 @@ +import React, { Fragment, useEffect } from "react"; import { Route, Routes } from "react-router-dom"; -import PrivateRoute from "./components/PrivateRoute"; +import { useDispatch, useSelector } from "react-redux"; + +import { ToastContainer } from "react-toastify"; +import axios from "axios"; + +import "react-toastify/dist/ReactToastify.css"; +import PrivateRoute from "./components/PrivateRoute"; import Layout from "./components/layout/Layout"; import Login from "./pages/login/Login"; import Register from "./pages/register/Register"; -import Navbar from "./components/navbar/Navbar"; +import PageNotFound from "./pages/404/PageNotFound"; +import httpIntercept from "./interceptor/interceptor"; +import { setCredentials } from "./store/slices/userSlice"; +import Home from "./pages/home/Home"; +import MyBooks from "./pages/my_books/MyBooks"; +import MyRequests from "./pages/my_requests/MyRequests"; function App() { + httpIntercept(); + + const { authToken } = useSelector((state) => state.user); + const dispatch = useDispatch(); + + useEffect(() => { + const token = localStorage.getItem("authToken"); + if (token) { + axios + .get(`${process.env.REACT_APP_BACKEND_URL}/auth/users/me`) + .then(({ data }) => { + dispatch(setCredentials(data)); + }); + } + }, [dispatch, authToken]); + return ( - <> - + - } /> - } /> - }> - } /> + }> + } /> + }/> + + } /> + } /> + + }> + } /> + } /> + - + + ); } diff --git a/src/components/PrivateRoute.jsx b/src/components/PrivateRoute.jsx index f3af732..b802868 100644 --- a/src/components/PrivateRoute.jsx +++ b/src/components/PrivateRoute.jsx @@ -1,10 +1,11 @@ -import React from 'react'; -import { Navigate, Outlet } from 'react-router-dom'; +import React from "react"; +import { useSelector } from "react-redux"; +import { Navigate, Outlet } from "react-router-dom"; const PrivateRoute = () => { - const auth = null; // TODO: determine if authorized, from redux later. + const { authToken } = useSelector((state) => state.user); - return auth ? : ; -} + return authToken ? : ; +}; export default PrivateRoute; diff --git a/src/components/Routes.jsx b/src/components/Routes.jsx deleted file mode 100644 index ac22b2b..0000000 --- a/src/components/Routes.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from "react"; -import { Route, Routes, Navigate } from "react-router-dom"; - -const AppRoutes = () => { - return ( - - } /> - - ); -}; - -export default AppRoutes; diff --git a/src/components/layout/Layout.jsx b/src/components/layout/Layout.jsx index 672e84e..31aa1ba 100644 --- a/src/components/layout/Layout.jsx +++ b/src/components/layout/Layout.jsx @@ -1,16 +1,20 @@ -import React from "react"; -import AppRoutes from "../Routes"; -import { Route } from "react-router-dom"; +import React, { Fragment } from "react"; +import { Outlet } from "react-router-dom"; + +import Container from "@mui/material/Container"; +import CssBaseline from "@mui/material/CssBaseline"; + +import Navbar from "../navbar/Navbar"; const Layout = () => { return ( - ( -
- -
- )} - /> + + + + + + + ); }; diff --git a/src/components/navbar/Navbar.jsx b/src/components/navbar/Navbar.jsx index 22e7d79..4eea8f9 100644 --- a/src/components/navbar/Navbar.jsx +++ b/src/components/navbar/Navbar.jsx @@ -1,170 +1,87 @@ import React, { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { useDispatch, useSelector } from "react-redux"; + import AppBar from "@mui/material/AppBar"; import Box from "@mui/material/Box"; import Toolbar from "@mui/material/Toolbar"; -import IconButton from "@mui/material/IconButton"; -import Typography from "@mui/material/Typography"; -import Menu from "@mui/material/Menu"; -import MenuIcon from "@mui/icons-material/Menu"; import Container from "@mui/material/Container"; -import Avatar from "@mui/material/Avatar"; import Button from "@mui/material/Button"; -import Tooltip from "@mui/material/Tooltip"; -import MenuItem from "@mui/material/MenuItem"; import AdbIcon from "@mui/icons-material/Adb"; -const userPages = ["Books", "My Books", "My Requests"]; -const librarianPages = ["Books", "Books Loans", "Book Requests"]; -const anonPages = ["Books"]; -const settings = ["Profile", "Account", "Dashboard", "Logout"]; +import { logout } from "../../store/slices/userSlice"; + +const userPages = [ + { name: "My Books", path: "lms/mybooks" }, + { name: "My Requests", path: "lms/myrequests" }, +]; +const librarianPages = ["Books Loans", "Book Requests"]; function ResponsiveAppBar() { - const [anchorElNav, setAnchorElNav] = useState(null); - const [anchorElUser, setAnchorElUser] = useState(null); - const [pages, setPages] = useState(anonPages); - const [user] = useState({ isAuth: false, isLibrarian: false }); + const [pages, setPages] = useState([]); + const { userInfo } = useSelector((state) => state.user); + + const dispatch = useDispatch(); + const navigate = useNavigate(); useEffect(() => { - if (user.isLibrarian) { + if (userInfo?.isLibrarian) { setPages(librarianPages); - } else if (user.isAuth) { + } else if (userInfo) { setPages(userPages); + } else { + setPages([]); } - }, [user]); - - const handleOpenNavMenu = (event) => { - setAnchorElNav(event.currentTarget); - }; - const handleOpenUserMenu = (event) => { - setAnchorElUser(event.currentTarget); - }; - - const handleCloseNavMenu = () => { - setAnchorElNav(null); - }; - - const handleCloseUserMenu = () => { - setAnchorElUser(null); - }; + }, [userInfo]); return ( - navigate("/")} + sx={{ my: 2, color: "white", display: "block" }} > - LOGO - + Home + - - - - - - {pages.map((page) => ( - - {page} - - ))} - - - - - LMS - {pages.map((page) => ( ))} - - - - - - - {settings.map((setting) => ( - - {setting} - - ))} - + {userInfo ? ( + + ) : ( + + + + + )} diff --git a/src/index.js b/src/index.js index b390529..0103966 100644 --- a/src/index.js +++ b/src/index.js @@ -5,16 +5,18 @@ import App from "./App"; import reportWebVitals from "./reportWebVitals"; import { BrowserRouter } from "react-router-dom"; +import { store } from "./store/Store"; +import { Provider } from "react-redux"; + const root = ReactDOM.createRoot(document.getElementById("root")); root.render( - + + + ); -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals(); diff --git a/src/interceptor/interceptor.js b/src/interceptor/interceptor.js new file mode 100644 index 0000000..f9134a0 --- /dev/null +++ b/src/interceptor/interceptor.js @@ -0,0 +1,28 @@ +import axios from "axios"; + +const httpIntercept = (props) => { + axios.interceptors.request.use( + (request) => { + if (request.url.includes(process.env.REACT_APP_BACKEND_URL)) { + request.headers.Authorization = + "JWT " + localStorage.getItem("authToken"); + } + return request; + }, + (error) => { + return Promise.reject(error); + } + ); + + axios.interceptors.response.use( + (response) => { + // Edit response config + return response; + }, + (error) => { + return Promise.reject(error); + } + ); +}; + +export default httpIntercept; diff --git a/src/pages/404/PageNotFound.jsx b/src/pages/404/PageNotFound.jsx new file mode 100644 index 0000000..362b116 --- /dev/null +++ b/src/pages/404/PageNotFound.jsx @@ -0,0 +1,9 @@ +import React from 'react' + +const PageNotFound = () => { + return ( +
PageNotFound
+ ) +} + +export default PageNotFound \ No newline at end of file diff --git a/src/pages/login/Login.jsx b/src/pages/login/Login.jsx index 63b98c6..8ef42bd 100644 --- a/src/pages/login/Login.jsx +++ b/src/pages/login/Login.jsx @@ -1,88 +1,101 @@ -import React from "react"; +import React, { useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { useDispatch, useSelector } from "react-redux"; + import Avatar from "@mui/material/Avatar"; -import Button from "@mui/material/Button"; -import CssBaseline from "@mui/material/CssBaseline"; import TextField from "@mui/material/TextField"; import Link from "@mui/material/Link"; import Grid from "@mui/material/Grid"; import Box from "@mui/material/Box"; import LockOutlinedIcon from "@mui/icons-material/LockOutlined"; import Typography from "@mui/material/Typography"; -import Container from "@mui/material/Container"; - +import LoadingButton from "@mui/lab/LoadingButton"; +import Alert from "@mui/material/Alert"; +import { userLogin } from "../../store/actions/userActions"; export default function SignIn() { + const { loading, userInfo, error } = useSelector((state) => state.user); + + const dispatch = useDispatch(); + const navigate = useNavigate(); + + // redirect authenticated user to profile screen + useEffect(() => { + if (userInfo) { + navigate("/"); + } + }, [navigate, userInfo]); + const handleSubmit = (event) => { event.preventDefault(); const data = new FormData(event.currentTarget); - console.log({ - email: data.get("email"), - password: data.get("password"), - }); + dispatch( + userLogin({ + username: data.get("username"), + password: data.get("password"), + }) + ); }; return ( - - - + + + + + Sign in + + + + + {error?.detail && ( + + {error?.detail} + + )} + - - - - - Sign in - - - - - - - - - - {"Don't have an account? Sign Up"} - - - - - - + Sign In + + + + + {"Don't have an account? Sign Up"} + + + + + ); } diff --git a/src/pages/my_books/MyBooks.jsx b/src/pages/my_books/MyBooks.jsx new file mode 100644 index 0000000..d3c07e4 --- /dev/null +++ b/src/pages/my_books/MyBooks.jsx @@ -0,0 +1,9 @@ +import React from 'react' + +const MyBooks = () => { + return ( +
MyBooks
+ ) +} + +export default MyBooks \ No newline at end of file diff --git a/src/pages/my_requests/MyRequests.jsx b/src/pages/my_requests/MyRequests.jsx new file mode 100644 index 0000000..dd1c124 --- /dev/null +++ b/src/pages/my_requests/MyRequests.jsx @@ -0,0 +1,9 @@ +import React from 'react' + +const MyRequests = () => { + return ( +
MyRequests
+ ) +} + +export default MyRequests \ No newline at end of file diff --git a/src/store/Store.js b/src/store/Store.js new file mode 100644 index 0000000..1835217 --- /dev/null +++ b/src/store/Store.js @@ -0,0 +1,8 @@ +import { configureStore } from '@reduxjs/toolkit' +import userReducer from './slices/userSlice' + +export const store = configureStore({ + reducer: { + user: userReducer, + }, +}) diff --git a/src/store/actions/userActions.jsx b/src/store/actions/userActions.jsx new file mode 100644 index 0000000..1309505 --- /dev/null +++ b/src/store/actions/userActions.jsx @@ -0,0 +1,24 @@ +import axios from "axios"; +import { createAsyncThunk } from "@reduxjs/toolkit"; +import { toast } from "react-toastify"; + +export const userLogin = createAsyncThunk( + "auth/login", + async ({ username, password }, { rejectWithValue }) => { + try { + const { data } = await axios.post( + `${process.env.REACT_APP_BACKEND_URL}/auth/jwt/create`, + { username, password } + ); + localStorage.setItem("authToken", data.access); + return data; + } catch (error) { + if (error.response && error.response.data.detail) { + return rejectWithValue(error.response.data); + } else { + toast.error(error.message); + return rejectWithValue(error.message); + } + } + } +); diff --git a/src/store/slices/userSlice.jsx b/src/store/slices/userSlice.jsx new file mode 100644 index 0000000..b36bc84 --- /dev/null +++ b/src/store/slices/userSlice.jsx @@ -0,0 +1,51 @@ +import { createSlice } from "@reduxjs/toolkit"; + +import { userLogin } from "../actions/userActions"; + +const authToken = localStorage.getItem("authToken") + ? localStorage.getItem("authToken") + : null; + +const initialState = { + loading: false, + userInfo: null, + authToken, + error: null, + success: false, +}; + +export const userSlice = createSlice({ + name: "user", + initialState, + reducers: { + logout: (state, { payload }) => { + localStorage.removeItem("authToken"); + state.loading = false + state.userInfo = null + state.authToken = null + state.error = null + }, + setCredentials: (state, { payload }) => { + state.userInfo = payload; + }, + }, + extraReducers: { + // login user + [userLogin.pending]: (state) => { + state.loading = true; + state.error = null; + }, + [userLogin.fulfilled]: (state, { payload }) => { + state.loading = false; + state.authToken = payload.access; + }, + [userLogin.rejected]: (state, { payload }) => { + state.loading = false; + state.error = payload; + }, + }, +}); + + +export const { logout, setCredentials } = userSlice.actions +export default userSlice.reducer; From ad4a0ec54f2621668147e58cb5451ba53b7b23b3 Mon Sep 17 00:00:00 2001 From: Ahmed Khalid <106074266+ahmed-arb@users.noreply.github.com> Date: Tue, 28 Feb 2023 18:11:05 +0500 Subject: [PATCH 4/6] add home page and book loans --- package-lock.json | 224 ++++++++++++++++++ package.json | 3 + src/App.js | 4 +- src/components/forms/BookLoanForm.jsx | 127 ++++++++++ src/components/navbar/Navbar.jsx | 9 +- src/pages/book_loans/BookLoans.jsx | 103 ++++++++ .../BookRequests.jsx} | 0 src/pages/home/Home.jsx | 86 ++++++- src/pages/my_books/MyBooks.jsx | 9 - 9 files changed, 546 insertions(+), 19 deletions(-) create mode 100644 src/components/forms/BookLoanForm.jsx create mode 100644 src/pages/book_loans/BookLoans.jsx rename src/pages/{my_requests/MyRequests.jsx => book_requests/BookRequests.jsx} (100%) delete mode 100644 src/pages/my_books/MyBooks.jsx diff --git a/package-lock.json b/package-lock.json index 298c884..5f60a6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,11 +13,14 @@ "@mui/icons-material": "^5.11.0", "@mui/lab": "^5.0.0-alpha.119", "@mui/material": "^5.11.7", + "@mui/x-date-pickers": "^5.0.20", "@reduxjs/toolkit": "^1.9.2", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "axios": "^1.3.2", + "moment": "^2.29.4", + "momentjs": "^2.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^8.0.5", @@ -2134,6 +2137,75 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@date-io/core": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@date-io/core/-/core-2.16.0.tgz", + "integrity": "sha512-DYmSzkr+jToahwWrsiRA2/pzMEtz9Bq1euJwoOuYwuwIYXnZFtHajY2E6a1VNVDc9jP8YUXK1BvnZH9mmT19Zg==" + }, + "node_modules/@date-io/date-fns": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-2.16.0.tgz", + "integrity": "sha512-bfm5FJjucqlrnQcXDVU5RD+nlGmL3iWgkHTq3uAZWVIuBu6dDmGa3m8a6zo2VQQpu8ambq9H22UyUpn7590joA==", + "dependencies": { + "@date-io/core": "^2.16.0" + }, + "peerDependencies": { + "date-fns": "^2.0.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + } + } + }, + "node_modules/@date-io/dayjs": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@date-io/dayjs/-/dayjs-2.16.0.tgz", + "integrity": "sha512-y5qKyX2j/HG3zMvIxTobYZRGnd1FUW2olZLS0vTj7bEkBQkjd2RO7/FEwDY03Z1geVGlXKnzIATEVBVaGzV4Iw==", + "dependencies": { + "@date-io/core": "^2.16.0" + }, + "peerDependencies": { + "dayjs": "^1.8.17" + }, + "peerDependenciesMeta": { + "dayjs": { + "optional": true + } + } + }, + "node_modules/@date-io/luxon": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/@date-io/luxon/-/luxon-2.16.1.tgz", + "integrity": "sha512-aeYp5K9PSHV28946pC+9UKUi/xMMYoaGelrpDibZSgHu2VWHXrr7zWLEr+pMPThSs5vt8Ei365PO+84pCm37WQ==", + "dependencies": { + "@date-io/core": "^2.16.0" + }, + "peerDependencies": { + "luxon": "^1.21.3 || ^2.x || ^3.x" + }, + "peerDependenciesMeta": { + "luxon": { + "optional": true + } + } + }, + "node_modules/@date-io/moment": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/@date-io/moment/-/moment-2.16.1.tgz", + "integrity": "sha512-JkxldQxUqZBfZtsaCcCMkm/dmytdyq5pS1RxshCQ4fHhsvP5A7gSqPD22QbVXMcJydi3d3v1Y8BQdUKEuGACZQ==", + "dependencies": { + "@date-io/core": "^2.16.0" + }, + "peerDependencies": { + "moment": "^2.24.0" + }, + "peerDependenciesMeta": { + "moment": { + "optional": true + } + } + }, "node_modules/@emotion/babel-plugin": { "version": "11.10.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.5.tgz", @@ -3476,6 +3548,64 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, + "node_modules/@mui/x-date-pickers": { + "version": "5.0.20", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-5.0.20.tgz", + "integrity": "sha512-ERukSeHIoNLbI1C2XRhF9wRhqfsr+Q4B1SAw2ZlU7CWgcG8UBOxgqRKDEOVAIoSWL+DWT6GRuQjOKvj6UXZceA==", + "dependencies": { + "@babel/runtime": "^7.18.9", + "@date-io/core": "^2.15.0", + "@date-io/date-fns": "^2.15.0", + "@date-io/dayjs": "^2.15.0", + "@date-io/luxon": "^2.15.0", + "@date-io/moment": "^2.15.0", + "@mui/utils": "^5.10.3", + "@types/react-transition-group": "^4.4.5", + "clsx": "^1.2.1", + "prop-types": "^15.7.2", + "react-transition-group": "^4.4.5", + "rifm": "^0.12.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.4.1", + "@mui/system": "^5.4.1", + "date-fns": "^2.25.0", + "dayjs": "^1.10.7", + "luxon": "^1.28.0 || ^2.0.0 || ^3.0.0", + "moment": "^2.29.1", + "react": "^17.0.2 || ^18.0.0", + "react-dom": "^17.0.2 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -12622,6 +12752,20 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "engines": { + "node": "*" + } + }, + "node_modules/momentjs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/momentjs/-/momentjs-2.0.0.tgz", + "integrity": "sha512-GYMUxLyCwVhECkJR1/LMHEyb9gWYSPRnXi+elGN0m5bet7ngQOxU4QLWUI/eBzgN4N/T194n6yP7lQiE+Udw9A==", + "deprecated": "WARNING: The correct package name for Moment.js is 'moment', not 'momentjs'." + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -15374,6 +15518,14 @@ "node": ">=0.10.0" } }, + "node_modules/rifm": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/rifm/-/rifm-0.12.1.tgz", + "integrity": "sha512-OGA1Bitg/dSJtI/c4dh90svzaUPt228kzFsUkJbtA2c964IqEAwWXeL9ZJi86xWv3j5SMqRvGULl7bA6cK0Bvg==", + "peerDependencies": { + "react": ">=16.8" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -19238,6 +19390,43 @@ "integrity": "sha512-jwx+WCqszn53YHOfvFMJJRd/B2GqkCBt+1MJSG6o5/s8+ytHMvDZXsJgUEWLk12UnLd7HYKac4BYU5i/Ron1Cw==", "requires": {} }, + "@date-io/core": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@date-io/core/-/core-2.16.0.tgz", + "integrity": "sha512-DYmSzkr+jToahwWrsiRA2/pzMEtz9Bq1euJwoOuYwuwIYXnZFtHajY2E6a1VNVDc9jP8YUXK1BvnZH9mmT19Zg==" + }, + "@date-io/date-fns": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-2.16.0.tgz", + "integrity": "sha512-bfm5FJjucqlrnQcXDVU5RD+nlGmL3iWgkHTq3uAZWVIuBu6dDmGa3m8a6zo2VQQpu8ambq9H22UyUpn7590joA==", + "requires": { + "@date-io/core": "^2.16.0" + } + }, + "@date-io/dayjs": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@date-io/dayjs/-/dayjs-2.16.0.tgz", + "integrity": "sha512-y5qKyX2j/HG3zMvIxTobYZRGnd1FUW2olZLS0vTj7bEkBQkjd2RO7/FEwDY03Z1geVGlXKnzIATEVBVaGzV4Iw==", + "requires": { + "@date-io/core": "^2.16.0" + } + }, + "@date-io/luxon": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/@date-io/luxon/-/luxon-2.16.1.tgz", + "integrity": "sha512-aeYp5K9PSHV28946pC+9UKUi/xMMYoaGelrpDibZSgHu2VWHXrr7zWLEr+pMPThSs5vt8Ei365PO+84pCm37WQ==", + "requires": { + "@date-io/core": "^2.16.0" + } + }, + "@date-io/moment": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/@date-io/moment/-/moment-2.16.1.tgz", + "integrity": "sha512-JkxldQxUqZBfZtsaCcCMkm/dmytdyq5pS1RxshCQ4fHhsvP5A7gSqPD22QbVXMcJydi3d3v1Y8BQdUKEuGACZQ==", + "requires": { + "@date-io/core": "^2.16.0" + } + }, "@emotion/babel-plugin": { "version": "11.10.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.5.tgz", @@ -20149,6 +20338,25 @@ } } }, + "@mui/x-date-pickers": { + "version": "5.0.20", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-5.0.20.tgz", + "integrity": "sha512-ERukSeHIoNLbI1C2XRhF9wRhqfsr+Q4B1SAw2ZlU7CWgcG8UBOxgqRKDEOVAIoSWL+DWT6GRuQjOKvj6UXZceA==", + "requires": { + "@babel/runtime": "^7.18.9", + "@date-io/core": "^2.15.0", + "@date-io/date-fns": "^2.15.0", + "@date-io/dayjs": "^2.15.0", + "@date-io/luxon": "^2.15.0", + "@date-io/moment": "^2.15.0", + "@mui/utils": "^5.10.3", + "@types/react-transition-group": "^4.4.5", + "clsx": "^1.2.1", + "prop-types": "^15.7.2", + "react-transition-group": "^4.4.5", + "rifm": "^0.12.1" + } + }, "@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -26831,6 +27039,16 @@ "minimist": "^1.2.6" } }, + "moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" + }, + "momentjs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/momentjs/-/momentjs-2.0.0.tgz", + "integrity": "sha512-GYMUxLyCwVhECkJR1/LMHEyb9gWYSPRnXi+elGN0m5bet7ngQOxU4QLWUI/eBzgN4N/T194n6yP7lQiE+Udw9A==" + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -28621,6 +28839,12 @@ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" }, + "rifm": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/rifm/-/rifm-0.12.1.tgz", + "integrity": "sha512-OGA1Bitg/dSJtI/c4dh90svzaUPt228kzFsUkJbtA2c964IqEAwWXeL9ZJi86xWv3j5SMqRvGULl7bA6cK0Bvg==", + "requires": {} + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", diff --git a/package.json b/package.json index 625565a..b5be625 100644 --- a/package.json +++ b/package.json @@ -8,11 +8,14 @@ "@mui/icons-material": "^5.11.0", "@mui/lab": "^5.0.0-alpha.119", "@mui/material": "^5.11.7", + "@mui/x-date-pickers": "^5.0.20", "@reduxjs/toolkit": "^1.9.2", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "axios": "^1.3.2", + "moment": "^2.29.4", + "momentjs": "^2.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^8.0.5", diff --git a/src/App.js b/src/App.js index 287c94b..ed52e70 100644 --- a/src/App.js +++ b/src/App.js @@ -15,8 +15,8 @@ import PageNotFound from "./pages/404/PageNotFound"; import httpIntercept from "./interceptor/interceptor"; import { setCredentials } from "./store/slices/userSlice"; import Home from "./pages/home/Home"; -import MyBooks from "./pages/my_books/MyBooks"; -import MyRequests from "./pages/my_requests/MyRequests"; +import MyBooks from "./pages/book_loans/BookLoans"; +import MyRequests from "./pages/book_requests/BookRequests"; function App() { httpIntercept(); diff --git a/src/components/forms/BookLoanForm.jsx b/src/components/forms/BookLoanForm.jsx new file mode 100644 index 0000000..b0acf0f --- /dev/null +++ b/src/components/forms/BookLoanForm.jsx @@ -0,0 +1,127 @@ +import React, { useEffect, useState } from "react"; + +import Button from "@mui/material/Button"; +import TextField from "@mui/material/TextField"; +import Grid from "@mui/material/Grid"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import Select from "@mui/material/Select"; +import MenuItem from "@mui/material/MenuItem"; +import FormControl from "@mui/material/FormControl"; +import InputLabel from "@mui/material/InputLabel"; +import { DesktopDatePicker } from "@mui/x-date-pickers/DesktopDatePicker"; +import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; +import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment"; + +import axios from "axios"; +import { toast } from "react-toastify"; +import moment from "moment"; + +const BookLoanForm = ({ bookLoan, handleClose }) => { + const [status, setStatus] = useState(""); + const [dateBorrowed, setDateBorrowed] = useState(null); + const [dateDue, setDateDue] = useState(null); + const [dateReturned, setDateReturned] = useState(null); + + useEffect(() => { + console.log(bookLoan); + setStatus(bookLoan.status); + setDateBorrowed(bookLoan.date_borrowed); + setDateDue(bookLoan.date_due); + setDateReturned(bookLoan.date_returned); + }, [bookLoan]); + + const handleStatusChange = (event) => { + setStatus(event.target.value); + }; + + const handleSubmit = (event) => { + event.preventDefault(); + axios + .put(`${process.env.REACT_APP_BACKEND_URL}/loans/${bookLoan.id}/`, { + book: bookLoan.book.id, + status: status, + date_borrowed: moment(dateBorrowed).format("YYYY-MM-DD"), + date_due: moment(dateDue).format("YYYY-MM-DD"), + date_returned: moment(dateReturned).format("YYYY-MM-DD"), + }) + .then(({ data }) => { + toast.success("Book loan updated successfully."); + }) + .catch((err) => { + toast.error(err.msg); + }); + handleClose() + }; + + return ( + + + Sign up + + + + + + Status + + + + + + } + /> + + + } + /> + + + { + setDateReturned(value); + }} + renderInput={(params) => } + /> + + + + + + + ); +}; + +export default BookLoanForm; diff --git a/src/components/navbar/Navbar.jsx b/src/components/navbar/Navbar.jsx index 4eea8f9..aaf20ce 100644 --- a/src/components/navbar/Navbar.jsx +++ b/src/components/navbar/Navbar.jsx @@ -12,10 +12,13 @@ import AdbIcon from "@mui/icons-material/Adb"; import { logout } from "../../store/slices/userSlice"; const userPages = [ - { name: "My Books", path: "lms/mybooks" }, - { name: "My Requests", path: "lms/myrequests" }, + { name: "Book Loans", path: "lms/mybooks" }, + { name: "Book Requests", path: "lms/myrequests" }, +]; +const librarianPages = [ + { name: "Book Loans", path: "lms/mybooks" }, + { name: "Book Requests", path: "lms/myrequests" }, ]; -const librarianPages = ["Books Loans", "Book Requests"]; function ResponsiveAppBar() { const [pages, setPages] = useState([]); diff --git a/src/pages/book_loans/BookLoans.jsx b/src/pages/book_loans/BookLoans.jsx new file mode 100644 index 0000000..1fe8b81 --- /dev/null +++ b/src/pages/book_loans/BookLoans.jsx @@ -0,0 +1,103 @@ +import React, { useEffect, useState } from "react"; +import axios from "axios"; + +import Table from "@mui/material/Table"; +import TableBody from "@mui/material/TableBody"; +import TableCell from "@mui/material/TableCell"; +import TableContainer from "@mui/material/TableContainer"; +import TableHead from "@mui/material/TableHead"; +import TableRow from "@mui/material/TableRow"; +import Paper from "@mui/material/Paper"; +import Box from "@mui/material/Box"; +import Button from "@mui/material/Button"; +import Dialog from "@mui/material/Dialog"; +import DialogContent from "@mui/material/DialogContent"; + +import { useSelector } from "react-redux"; + +import BookLoanForm from "../../components/forms/BookLoanForm"; + + +const MyBooks = () => { + const [loans, setLoans] = useState([]); + const [defaultValue, setDefaultValue] = useState(null); + const [open, setOpen] = useState(false); + + const { userInfo } = useSelector((state) => state.user); + + const handleOpen = () => setOpen(true); + const handleClose = () => setOpen(false); + + useEffect(() => { + axios.get(`${process.env.REACT_APP_BACKEND_URL}/loans`).then(({ data }) => { + setLoans(data); + }); + }, []); + return ( + + + + + + Book + Status + Loaned + Due + Returned + {userInfo?.is_librarian && ( + Actions + )} + + + + {loans.map((row) => ( + + + {row.book.name} + + {row.status} + {row.date_borrowed} + {row.date_due} + + {row.date_returned ?? "N/A"} + + {userInfo?.is_librarian && ( + + {" "} + + + )} + + ))} + +
+
+ + + + + +
+ ); +}; + +export default MyBooks; diff --git a/src/pages/my_requests/MyRequests.jsx b/src/pages/book_requests/BookRequests.jsx similarity index 100% rename from src/pages/my_requests/MyRequests.jsx rename to src/pages/book_requests/BookRequests.jsx diff --git a/src/pages/home/Home.jsx b/src/pages/home/Home.jsx index 96c5535..c5dd1ba 100644 --- a/src/pages/home/Home.jsx +++ b/src/pages/home/Home.jsx @@ -1,9 +1,85 @@ -import React from 'react' +import React, { useEffect, useState } from "react"; +import Box from "@mui/material/Box"; +import Grid from "@mui/material/Grid"; +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import CardMedia from "@mui/material/CardMedia"; +import CardActions from "@mui/material/CardActions"; +import Button from "@mui/material/Button"; +import Typography from "@mui/material/Typography"; +import axios from "axios"; +import { toast } from "react-toastify"; const Home = () => { + const [books, setBooks] = useState([]); + useEffect(() => { + axios.get(`${process.env.REACT_APP_BACKEND_URL}/books`).then(({ data }) => { + setBooks(data); + }); + }, []); + + const handleLoan = (bookId) => { + axios + .post(`${process.env.REACT_APP_BACKEND_URL}/loans/`, { + book: bookId, + status: "requested", + }) + .then(({ data }) => { + toast.success( + 'Book loan successful. You can track your loan in "Book Loans".' + ); + }) + .catch((err) => { + toast.error(err.msg); + }); + }; return ( -
Home
- ) -} + + + {books.map((item) => ( + + + + + + {item.name} + + + {item.author} - {item.publisher} - {item.stock} available + + + + + + + + ))} + + + ); +}; -export default Home \ No newline at end of file +export default Home; diff --git a/src/pages/my_books/MyBooks.jsx b/src/pages/my_books/MyBooks.jsx deleted file mode 100644 index d3c07e4..0000000 --- a/src/pages/my_books/MyBooks.jsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react' - -const MyBooks = () => { - return ( -
MyBooks
- ) -} - -export default MyBooks \ No newline at end of file From 9a1cb1d1bb20f942fcaf0723bcec9b48954e9e9c Mon Sep 17 00:00:00 2001 From: Ahmed Khalid <106074266+ahmed-arb@users.noreply.github.com> Date: Wed, 1 Mar 2023 17:29:20 +0500 Subject: [PATCH 5/6] feat: add book requests module --- src/components/forms/BookLoanForm.jsx | 3 +- src/components/forms/LibBookRequestForm.jsx | 97 ++++++++++++++ src/components/forms/UserBookRequestForm.jsx | 81 ++++++++++++ src/interceptor/interceptor.js | 8 +- src/pages/book_loans/BookLoans.jsx | 36 ++++-- src/pages/book_requests/BookRequests.jsx | 129 ++++++++++++++++++- src/pages/home/Home.jsx | 23 ++-- src/pages/register/Register.jsx | 44 +++++-- 8 files changed, 382 insertions(+), 39 deletions(-) create mode 100644 src/components/forms/LibBookRequestForm.jsx create mode 100644 src/components/forms/UserBookRequestForm.jsx diff --git a/src/components/forms/BookLoanForm.jsx b/src/components/forms/BookLoanForm.jsx index b0acf0f..68c3479 100644 --- a/src/components/forms/BookLoanForm.jsx +++ b/src/components/forms/BookLoanForm.jsx @@ -24,7 +24,6 @@ const BookLoanForm = ({ bookLoan, handleClose }) => { const [dateReturned, setDateReturned] = useState(null); useEffect(() => { - console.log(bookLoan); setStatus(bookLoan.status); setDateBorrowed(bookLoan.date_borrowed); setDateDue(bookLoan.date_due); @@ -63,7 +62,7 @@ const BookLoanForm = ({ bookLoan, handleClose }) => { }} > - Sign up + Book Loan Form diff --git a/src/components/forms/LibBookRequestForm.jsx b/src/components/forms/LibBookRequestForm.jsx new file mode 100644 index 0000000..ca6a46e --- /dev/null +++ b/src/components/forms/LibBookRequestForm.jsx @@ -0,0 +1,97 @@ +import React, { useEffect, useState } from "react"; + +import Button from "@mui/material/Button"; +import TextField from "@mui/material/TextField"; +import Grid from "@mui/material/Grid"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import Select from "@mui/material/Select"; +import MenuItem from "@mui/material/MenuItem"; +import FormControl from "@mui/material/FormControl"; +import InputLabel from "@mui/material/InputLabel"; +import TextareaAutosize from "@mui/base/TextareaAutosize"; + +import axios from "axios"; +import { toast } from "react-toastify"; + +const BookLoanForm = ({ defaultValue, handleClose }) => { + const [status, setStatus] = useState(defaultValue?.status); + const [reason, setReason] = useState(defaultValue?.reason); + + const handleStatusChange = (event) => { + setStatus(event.target.value); + }; + + const handleSubmit = (event) => { + event.preventDefault(); + const data = new FormData(event.currentTarget); + + axios + .patch( + `${process.env.REACT_APP_BACKEND_URL}/book_requests/${defaultValue.id}/`, + { + // ...defaultValue, + status, + reason, + } + ) + .then(({ data }) => { + toast.success("Book request updated successfully."); + handleClose(); + }) + .catch((err) => { + toast.error(err.detail); + }); + }; + + return ( + + + Book Request + + + + + + Status + + + + + setReason(event.target.value)} + /> + + + + + + ); +}; + +export default BookLoanForm; diff --git a/src/components/forms/UserBookRequestForm.jsx b/src/components/forms/UserBookRequestForm.jsx new file mode 100644 index 0000000..8080940 --- /dev/null +++ b/src/components/forms/UserBookRequestForm.jsx @@ -0,0 +1,81 @@ +import React, { useEffect } from "react"; + +import Button from "@mui/material/Button"; +import TextField from "@mui/material/TextField"; +import Grid from "@mui/material/Grid"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; + +import axios from "axios"; +import { toast } from "react-toastify"; + +const UserBookRequestForm = ({ defaultValue, handleClose }) => { + + const handleSubmit = (event) => { + event.preventDefault(); + const data = new FormData(event.currentTarget); + + if (defaultValue) { + axios + .put( + `${process.env.REACT_APP_BACKEND_URL}/book_requests/${defaultValue.id}/`, + { + book_name: data.get("book_name"), + } + ) + .then(({ data }) => { + toast.success("Book request updated successfully."); + handleClose(); + }) + .catch((err) => { + toast.error(err.detail); + }); + } else { + axios + .post(`${process.env.REACT_APP_BACKEND_URL}/book_requests/`, { + book_name: data.get("book_name"), + }) + .then(({ data }) => { + toast.success("Book request created successfully."); + handleClose(); + }) + .catch((err) => { + toast.error(err.detail); + }); + } + }; + + return ( + + + Request A Book + + + + + + + + + + + ); +}; + +export default UserBookRequestForm; diff --git a/src/interceptor/interceptor.js b/src/interceptor/interceptor.js index f9134a0..d1408ba 100644 --- a/src/interceptor/interceptor.js +++ b/src/interceptor/interceptor.js @@ -3,9 +3,13 @@ import axios from "axios"; const httpIntercept = (props) => { axios.interceptors.request.use( (request) => { - if (request.url.includes(process.env.REACT_APP_BACKEND_URL)) { + const authToken = localStorage.getItem("authToken"); + if ( + request.url.includes(process.env.REACT_APP_BACKEND_URL) && + authToken + ) { request.headers.Authorization = - "JWT " + localStorage.getItem("authToken"); + "JWT " + authToken; } return request; }, diff --git a/src/pages/book_loans/BookLoans.jsx b/src/pages/book_loans/BookLoans.jsx index 1fe8b81..7f413ea 100644 --- a/src/pages/book_loans/BookLoans.jsx +++ b/src/pages/book_loans/BookLoans.jsx @@ -16,23 +16,35 @@ import DialogContent from "@mui/material/DialogContent"; import { useSelector } from "react-redux"; import BookLoanForm from "../../components/forms/BookLoanForm"; - +import { toast } from "react-toastify"; const MyBooks = () => { const [loans, setLoans] = useState([]); const [defaultValue, setDefaultValue] = useState(null); + const [reload, setReload] = useState(0); const [open, setOpen] = useState(false); const { userInfo } = useSelector((state) => state.user); - + const handleOpen = () => setOpen(true); - const handleClose = () => setOpen(false); + const handleClose = () => { + setOpen(false); + setReload(Math.random()); + }; useEffect(() => { axios.get(`${process.env.REACT_APP_BACKEND_URL}/loans`).then(({ data }) => { setLoans(data); }); - }, []); + }, [reload]); + + const handleRemind = (id) => { + axios + .get(`${process.env.REACT_APP_BACKEND_URL}/loans/${id}/`) + .then((data) => { + toast.success("Reminder email sent."); + }); + }; return ( { {userInfo?.is_librarian && ( - {" "} + )} @@ -93,7 +111,7 @@ const MyBooks = () => { - + diff --git a/src/pages/book_requests/BookRequests.jsx b/src/pages/book_requests/BookRequests.jsx index dd1c124..22ad040 100644 --- a/src/pages/book_requests/BookRequests.jsx +++ b/src/pages/book_requests/BookRequests.jsx @@ -1,9 +1,128 @@ -import React from 'react' +import React, { useEffect, useState } from "react"; +import axios from "axios"; + +import Table from "@mui/material/Table"; +import TableBody from "@mui/material/TableBody"; +import TableCell from "@mui/material/TableCell"; +import TableContainer from "@mui/material/TableContainer"; +import TableHead from "@mui/material/TableHead"; +import TableRow from "@mui/material/TableRow"; +import Paper from "@mui/material/Paper"; +import Grid from "@mui/material/Grid"; +import Button from "@mui/material/Button"; +import Dialog from "@mui/material/Dialog"; +import DialogContent from "@mui/material/DialogContent"; + +import { useSelector } from "react-redux"; + +import UserBookRequestForm from "../../components/forms/UserBookRequestForm"; +import LibBookRequestForm from "../../components/forms/LibBookRequestForm"; +import moment from "moment"; const MyRequests = () => { + const [requests, setRequests] = useState([]); + const [defaultValue, setDefaultValue] = useState(null); + const [reload, setReload] = useState(0); + const [open, setOpen] = useState(false); + + const { userInfo } = useSelector((state) => state.user); + + const handleOpen = () => setOpen(true); + const handleClose = () => { + setDefaultValue(null); + setOpen(false); + setReload(Math.random()); + }; + + useEffect(() => { + axios + .get(`${process.env.REACT_APP_BACKEND_URL}/book_requests`) + .then(({ data }) => { + setRequests(data); + }); + }, [reload]); return ( -
MyRequests
- ) -} + + + + + + + + + + {userInfo?.is_librarian && ( + User + )} + Book + Status + Reason + Created At + Actions + + + + {requests.map((row) => ( + + {userInfo?.is_librarian && ( + {row.user} + )} + + {row.book_name} + + {row.status} + {row.reason ?? "N/A"} + + {moment(row.created_at).format("ll")} + + + + + + + ))} + +
+
+
+ + + + {userInfo?.is_librarian && defaultValue ? ( + + ) : ( + + )} + + +
+ ); +}; -export default MyRequests \ No newline at end of file +export default MyRequests; diff --git a/src/pages/home/Home.jsx b/src/pages/home/Home.jsx index c5dd1ba..f44c47f 100644 --- a/src/pages/home/Home.jsx +++ b/src/pages/home/Home.jsx @@ -9,9 +9,12 @@ import Button from "@mui/material/Button"; import Typography from "@mui/material/Typography"; import axios from "axios"; import { toast } from "react-toastify"; +import { useSelector } from "react-redux"; const Home = () => { const [books, setBooks] = useState([]); + const { authToken } = useSelector((state) => state.user); + useEffect(() => { axios.get(`${process.env.REACT_APP_BACKEND_URL}/books`).then(({ data }) => { setBooks(data); @@ -64,15 +67,17 @@ const Home = () => { - + {authToken && ( + + )}
diff --git a/src/pages/register/Register.jsx b/src/pages/register/Register.jsx index e86d4f3..aa40b13 100644 --- a/src/pages/register/Register.jsx +++ b/src/pages/register/Register.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, {useState} from "react"; import Avatar from "@mui/material/Avatar"; import Button from "@mui/material/Button"; import CssBaseline from "@mui/material/CssBaseline"; @@ -15,8 +15,13 @@ import MenuItem from "@mui/material/MenuItem"; import FormControl from "@mui/material/FormControl"; import InputLabel from "@mui/material/InputLabel"; +import axios from "axios"; +import {toast} from 'react-toastify' +import { useNavigate } from "react-router-dom"; + export default function SignUp() { - const [gender, setGender] = React.useState(""); + const [gender, setGender] = useState(""); + const navigate = useNavigate() const handleChange = (event) => { setGender(event.target.value); @@ -24,9 +29,15 @@ export default function SignUp() { const handleSubmit = (event) => { event.preventDefault(); const data = new FormData(event.currentTarget); - console.log({ - email: data.get("email"), - password: data.get("password"), + var newUser = Object.fromEntries(data) + newUser.gender = gender + axios.post( + `${process.env.REACT_APP_BACKEND_URL}/auth/users/`, + newUser + ).then(({data})=>{ + console.log(data) + toast.success("Welcome to the greatest library ever! try logging in.") + navigate("/login"); }); }; @@ -47,15 +58,15 @@ export default function SignUp() { Sign up - + @@ -64,12 +75,21 @@ export default function SignUp() { + + + - Male - Female + Male + Female Other From b8a1396738e618b6eab29dec53a59833427b5045 Mon Sep 17 00:00:00 2001 From: Ahmed Khalid <106074266+ahmed-arb@users.noreply.github.com> Date: Mon, 6 Mar 2023 14:29:18 +0500 Subject: [PATCH 6/6] change all requests to custom hook --- src/App.js | 25 ++++--- src/components/forms/BookLoanForm.jsx | 78 +++++++++++--------- src/components/forms/LibBookRequestForm.jsx | 57 +++++++------- src/components/forms/UserBookRequestForm.jsx | 44 +++++------ src/constants.js | 18 +++++ src/hooks/use-https.jsx | 32 ++++++++ src/pages/book_loans/BookLoans.jsx | 27 ++++--- src/pages/book_requests/BookRequests.jsx | 14 ++-- src/pages/home/Home.jsx | 29 ++++---- src/pages/login/Login.jsx | 1 + src/pages/register/Register.jsx | 36 ++++----- src/store/actions/userActions.jsx | 1 + 12 files changed, 222 insertions(+), 140 deletions(-) create mode 100644 src/constants.js create mode 100644 src/hooks/use-https.jsx diff --git a/src/App.js b/src/App.js index ed52e70..064965c 100644 --- a/src/App.js +++ b/src/App.js @@ -3,7 +3,6 @@ import { Route, Routes } from "react-router-dom"; import { useDispatch, useSelector } from "react-redux"; import { ToastContainer } from "react-toastify"; -import axios from "axios"; import "react-toastify/dist/ReactToastify.css"; @@ -17,37 +16,43 @@ import { setCredentials } from "./store/slices/userSlice"; import Home from "./pages/home/Home"; import MyBooks from "./pages/book_loans/BookLoans"; import MyRequests from "./pages/book_requests/BookRequests"; +import useHttp from "./hooks/use-https"; function App() { httpIntercept(); const { authToken } = useSelector((state) => state.user); const dispatch = useDispatch(); + const { sendRequest } = useHttp(); useEffect(() => { const token = localStorage.getItem("authToken"); if (token) { - axios - .get(`${process.env.REACT_APP_BACKEND_URL}/auth/users/me`) - .then(({ data }) => { + + sendRequest( + { + url: "auth/users/me/", + }, + (data) => { dispatch(setCredentials(data)); - }); + } + ); } - }, [dispatch, authToken]); + }, [dispatch, authToken, sendRequest]); return ( }> } /> - }/> + } /> } /> } /> - }> - } /> - } /> + }> + } /> + } /> diff --git a/src/components/forms/BookLoanForm.jsx b/src/components/forms/BookLoanForm.jsx index 68c3479..3889b92 100644 --- a/src/components/forms/BookLoanForm.jsx +++ b/src/components/forms/BookLoanForm.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useState } from "react"; import Button from "@mui/material/Button"; import TextField from "@mui/material/TextField"; @@ -13,22 +13,17 @@ import { DesktopDatePicker } from "@mui/x-date-pickers/DesktopDatePicker"; import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment"; -import axios from "axios"; import { toast } from "react-toastify"; import moment from "moment"; +import { bookLoanStatusOptions } from "../../constants"; +import useHttp from "../../hooks/use-https"; const BookLoanForm = ({ bookLoan, handleClose }) => { - const [status, setStatus] = useState(""); + const [status, setStatus] = useState(null); const [dateBorrowed, setDateBorrowed] = useState(null); const [dateDue, setDateDue] = useState(null); const [dateReturned, setDateReturned] = useState(null); - - useEffect(() => { - setStatus(bookLoan.status); - setDateBorrowed(bookLoan.date_borrowed); - setDateDue(bookLoan.date_due); - setDateReturned(bookLoan.date_returned); - }, [bookLoan]); + const { sendRequest, isLoading } = useHttp(); const handleStatusChange = (event) => { setStatus(event.target.value); @@ -36,21 +31,29 @@ const BookLoanForm = ({ bookLoan, handleClose }) => { const handleSubmit = (event) => { event.preventDefault(); - axios - .put(`${process.env.REACT_APP_BACKEND_URL}/loans/${bookLoan.id}/`, { - book: bookLoan.book.id, - status: status, - date_borrowed: moment(dateBorrowed).format("YYYY-MM-DD"), - date_due: moment(dateDue).format("YYYY-MM-DD"), - date_returned: moment(dateReturned).format("YYYY-MM-DD"), - }) - .then(({ data }) => { - toast.success("Book loan updated successfully."); - }) - .catch((err) => { - toast.error(err.msg); - }); - handleClose() + sendRequest( + { + url: `loans/${bookLoan.id}/`, + method: "PUT", + body: { + book: bookLoan.book.id, + status: status ?? bookLoan.status, + date_borrowed: dateBorrowed + ? moment(dateBorrowed).format("YYYY-MM-DD") + : bookLoan.date_borrowed, + date_due: dateDue + ? moment(dateDue).format("YYYY-MM-DD") + : bookLoan.date_due, + date_returned: dateReturned + ? moment(dateReturned).format("YYYY-MM-DD") + : bookLoan.date_returned, + }, + }, + (data) => { + toast.success("yay"); + handleClose(); + } + ); }; return ( @@ -62,7 +65,7 @@ const BookLoanForm = ({ bookLoan, handleClose }) => { }} > - Book Loan Form + Book Loan Form @@ -72,23 +75,23 @@ const BookLoanForm = ({ bookLoan, handleClose }) => { } /> @@ -97,7 +100,7 @@ const BookLoanForm = ({ bookLoan, handleClose }) => { } /> @@ -106,7 +109,7 @@ const BookLoanForm = ({ bookLoan, handleClose }) => { { setDateReturned(value); }} @@ -115,7 +118,12 @@ const BookLoanForm = ({ bookLoan, handleClose }) => { - diff --git a/src/components/forms/LibBookRequestForm.jsx b/src/components/forms/LibBookRequestForm.jsx index ca6a46e..b08c511 100644 --- a/src/components/forms/LibBookRequestForm.jsx +++ b/src/components/forms/LibBookRequestForm.jsx @@ -1,22 +1,23 @@ -import React, { useEffect, useState } from "react"; +import React, { useState } from "react"; import Button from "@mui/material/Button"; -import TextField from "@mui/material/TextField"; import Grid from "@mui/material/Grid"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import Select from "@mui/material/Select"; import MenuItem from "@mui/material/MenuItem"; import FormControl from "@mui/material/FormControl"; -import InputLabel from "@mui/material/InputLabel"; import TextareaAutosize from "@mui/base/TextareaAutosize"; +import InputLabel from "@mui/material/InputLabel"; -import axios from "axios"; import { toast } from "react-toastify"; +import { bookRequestStatusOptions } from "../../constants"; +import useHttp from "../../hooks/use-https"; const BookLoanForm = ({ defaultValue, handleClose }) => { - const [status, setStatus] = useState(defaultValue?.status); - const [reason, setReason] = useState(defaultValue?.reason); + const [status, setStatus] = useState(null); + const [reason, setReason] = useState(null); + const { sendRequest, isLoading } = useHttp(); const handleStatusChange = (event) => { setStatus(event.target.value); @@ -24,24 +25,21 @@ const BookLoanForm = ({ defaultValue, handleClose }) => { const handleSubmit = (event) => { event.preventDefault(); - const data = new FormData(event.currentTarget); - axios - .patch( - `${process.env.REACT_APP_BACKEND_URL}/book_requests/${defaultValue.id}/`, - { - // ...defaultValue, - status, - reason, - } - ) - .then(({ data }) => { + sendRequest( + { + url: `book_requests/${defaultValue.id}/`, + method: "PATCH", + body: { + status: status ?? defaultValue.status, + reason: reason ?? defaultValue.reason, + }, + }, + (data) => { toast.success("Book request updated successfully."); handleClose(); - }) - .catch((err) => { - toast.error(err.detail); - }); + } + ); }; return ( @@ -63,13 +61,13 @@ const BookLoanForm = ({ defaultValue, handleClose }) => { @@ -80,13 +78,18 @@ const BookLoanForm = ({ defaultValue, handleClose }) => { minRows={10} placeholder="Reason for rejection" style={{ width: 400 }} - value={reason} + value={reason ?? defaultValue?.reason} disabled={status !== "rejected"} onChange={(event) => setReason(event.target.value)} /> - diff --git a/src/components/forms/UserBookRequestForm.jsx b/src/components/forms/UserBookRequestForm.jsx index 8080940..c29d72d 100644 --- a/src/components/forms/UserBookRequestForm.jsx +++ b/src/components/forms/UserBookRequestForm.jsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React from "react"; import Button from "@mui/material/Button"; import TextField from "@mui/material/TextField"; @@ -6,42 +6,42 @@ import Grid from "@mui/material/Grid"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; -import axios from "axios"; import { toast } from "react-toastify"; +import useHttp from "../../hooks/use-https"; const UserBookRequestForm = ({ defaultValue, handleClose }) => { + const { sendRequest } = useHttp(); const handleSubmit = (event) => { event.preventDefault(); const data = new FormData(event.currentTarget); if (defaultValue) { - axios - .put( - `${process.env.REACT_APP_BACKEND_URL}/book_requests/${defaultValue.id}/`, - { + sendRequest( + { + url: `book_requests/${defaultValue.id}/`, + method: "PUT", + body: { book_name: data.get("book_name"), - } - ) - .then(({ data }) => { + }, + }, + (data) => { toast.success("Book request updated successfully."); handleClose(); - }) - .catch((err) => { - toast.error(err.detail); - }); + } + ); } else { - axios - .post(`${process.env.REACT_APP_BACKEND_URL}/book_requests/`, { - book_name: data.get("book_name"), - }) - .then(({ data }) => { + sendRequest( + { + url: "book_requests/", + method: "POST", + body: { book_name: data.get("book_name") }, + }, + (data) => { toast.success("Book request created successfully."); handleClose(); - }) - .catch((err) => { - toast.error(err.detail); - }); + } + ); } }; diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 0000000..b591b86 --- /dev/null +++ b/src/constants.js @@ -0,0 +1,18 @@ +export const userGenderOptions = [ + { value: 0, display: "Male" }, + { value: 1, display: "Female" }, + { value: 2, display: "Other" }, +]; + +export const bookRequestStatusOptions = [ + { value: 0, display: "Pending" }, + { value: 1, display: "Approved" }, + { value: 2, display: "Rejected" }, +]; + +export const bookLoanStatusOptions = [ + { value: 0, display: "Requested" }, + { value: 1, display: "Issued" }, + { value: 2, display: "Rejected" }, + { value: 3, display: "Returned" }, +]; diff --git a/src/hooks/use-https.jsx b/src/hooks/use-https.jsx new file mode 100644 index 0000000..6b49f43 --- /dev/null +++ b/src/hooks/use-https.jsx @@ -0,0 +1,32 @@ +import { useState, useCallback } from "react"; +import axios from "axios"; +import { toast } from "react-toastify"; + +const useHttp = () => { + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const sendRequest = useCallback(async (requestConfig, applyData) => { + setIsLoading(true); + setError(null); + try { + const response = await axios(`${process.env.REACT_APP_BACKEND_URL}/${requestConfig.url}`, { + method: requestConfig.method ?? "GET", + data: requestConfig.body ?? null, + }); + + applyData(response.data); + setIsLoading(false); + } catch (err) { + toast.error(err.message || "Something went wrong!"); + setError(err.message || "Something went wrong!"); + } + }, []); + + return { + isLoading, + error, + sendRequest, + }; +}; + +export default useHttp; diff --git a/src/pages/book_loans/BookLoans.jsx b/src/pages/book_loans/BookLoans.jsx index 7f413ea..da3c690 100644 --- a/src/pages/book_loans/BookLoans.jsx +++ b/src/pages/book_loans/BookLoans.jsx @@ -1,5 +1,4 @@ import React, { useEffect, useState } from "react"; -import axios from "axios"; import Table from "@mui/material/Table"; import TableBody from "@mui/material/TableBody"; @@ -17,6 +16,7 @@ import { useSelector } from "react-redux"; import BookLoanForm from "../../components/forms/BookLoanForm"; import { toast } from "react-toastify"; +import useHttp from "../../hooks/use-https"; const MyBooks = () => { const [loans, setLoans] = useState([]); @@ -25,6 +25,7 @@ const MyBooks = () => { const [open, setOpen] = useState(false); const { userInfo } = useSelector((state) => state.user); + const { sendRequest } = useHttp(); const handleOpen = () => setOpen(true); const handleClose = () => { @@ -33,17 +34,25 @@ const MyBooks = () => { }; useEffect(() => { - axios.get(`${process.env.REACT_APP_BACKEND_URL}/loans`).then(({ data }) => { - setLoans(data); - }); - }, [reload]); + sendRequest( + { + url: "loans", + }, + (data) => { + setLoans(data); + } + ); + }, [reload, sendRequest]); const handleRemind = (id) => { - axios - .get(`${process.env.REACT_APP_BACKEND_URL}/loans/${id}/`) - .then((data) => { + sendRequest( + { + url: `loans/${id}/`, + }, + (data) => { toast.success("Reminder email sent."); - }); + } + ); }; return ( { const [requests, setRequests] = useState([]); @@ -25,6 +25,8 @@ const MyRequests = () => { const [reload, setReload] = useState(0); const [open, setOpen] = useState(false); + const { sendRequest } = useHttp(); + const { userInfo } = useSelector((state) => state.user); const handleOpen = () => setOpen(true); @@ -35,12 +37,10 @@ const MyRequests = () => { }; useEffect(() => { - axios - .get(`${process.env.REACT_APP_BACKEND_URL}/book_requests`) - .then(({ data }) => { - setRequests(data); - }); - }, [reload]); + sendRequest({ url: "book_requests/" }, (data) => { + setRequests(data); + }); + }, [reload, sendRequest]); return ( { const [books, setBooks] = useState([]); const { authToken } = useSelector((state) => state.user); + const { sendRequest } = useHttp(); useEffect(() => { - axios.get(`${process.env.REACT_APP_BACKEND_URL}/books`).then(({ data }) => { + sendRequest({ url: "books/" }, (data) => { setBooks(data); }); - }, []); + }, [sendRequest]); const handleLoan = (bookId) => { - axios - .post(`${process.env.REACT_APP_BACKEND_URL}/loans/`, { - book: bookId, - status: "requested", - }) - .then(({ data }) => { + sendRequest( + { + url: "loans/", + method: "POST", + body: { + book: bookId, + status: "requested", + }, + }, + (data) => { toast.success( 'Book loan successful. You can track your loan in "Book Loans".' ); - }) - .catch((err) => { - toast.error(err.msg); - }); + } + ); }; return ( { if (userInfo) { + console.log("first") navigate("/"); } }, [navigate, userInfo]); diff --git a/src/pages/register/Register.jsx b/src/pages/register/Register.jsx index aa40b13..2891343 100644 --- a/src/pages/register/Register.jsx +++ b/src/pages/register/Register.jsx @@ -1,4 +1,4 @@ -import React, {useState} from "react"; +import React, { useState } from "react"; import Avatar from "@mui/material/Avatar"; import Button from "@mui/material/Button"; import CssBaseline from "@mui/material/CssBaseline"; @@ -15,13 +15,15 @@ import MenuItem from "@mui/material/MenuItem"; import FormControl from "@mui/material/FormControl"; import InputLabel from "@mui/material/InputLabel"; -import axios from "axios"; -import {toast} from 'react-toastify' +import { toast } from "react-toastify"; import { useNavigate } from "react-router-dom"; +import { userGenderOptions } from "../../constants"; +import useHttp from "../../hooks/use-https"; export default function SignUp() { const [gender, setGender] = useState(""); - const navigate = useNavigate() + const navigate = useNavigate(); + const { sendRequest } = useHttp(); const handleChange = (event) => { setGender(event.target.value); @@ -29,16 +31,16 @@ export default function SignUp() { const handleSubmit = (event) => { event.preventDefault(); const data = new FormData(event.currentTarget); - var newUser = Object.fromEntries(data) - newUser.gender = gender - axios.post( - `${process.env.REACT_APP_BACKEND_URL}/auth/users/`, - newUser - ).then(({data})=>{ - console.log(data) - toast.success("Welcome to the greatest library ever! try logging in.") - navigate("/login"); - }); + var newUser = Object.fromEntries(data); + newUser.gender = gender; + + sendRequest( + { url: "auth/users/", method: "POST", body: newUser }, + (data) => { + toast.success("Welcome to the greatest library ever! try logging in."); + navigate("/login"); + } + ); }; return ( @@ -121,9 +123,9 @@ export default function SignUp() { label="Gender" onChange={handleChange} > - Male - Female - Other + {userGenderOptions.map((option) => ( + {option.display} + ))} diff --git a/src/store/actions/userActions.jsx b/src/store/actions/userActions.jsx index 1309505..1c91cec 100644 --- a/src/store/actions/userActions.jsx +++ b/src/store/actions/userActions.jsx @@ -13,6 +13,7 @@ export const userLogin = createAsyncThunk( localStorage.setItem("authToken", data.access); return data; } catch (error) { + console.log("error") if (error.response && error.response.data.detail) { return rejectWithValue(error.response.data); } else {