diff --git a/web/package-lock.json b/web/package-lock.json index 202bff49067475b51b94053c6877e63a1c875fac..8d194350e6765bdc34341ef8a3abe07c7899b848 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -8,6 +8,8 @@ "name": "web", "version": "0.1.0", "dependencies": { + "@fortawesome/free-solid-svg-icons": "^6.5.1", + "@fortawesome/react-fontawesome": "^0.2.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -15,11 +17,18 @@ "@types/node": "^16.18.87", "@types/react": "^18.2.64", "@types/react-dom": "^18.2.21", + "axios": "^1.6.8", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hook-form": "^7.51.2", + "react-pro-sidebar": "^1.1.0", + "react-router-dom": "^6.22.3", "react-scripts": "5.0.1", "typescript": "^4.9.5", "web-vitals": "^2.1.4" + }, + "devDependencies": { + "tailwindcss": "^3.4.1" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -2288,6 +2297,163 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", + "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/serialize": "^1.1.2", + "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.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "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.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", + "dependencies": { + "@emotion/memoize": "^0.8.1", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", + "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/react": { + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz", + "integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/cache": "^11.11.0", + "@emotion/serialize": "^1.1.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.4.tgz", + "integrity": "sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==", + "dependencies": { + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/unitless": "^0.8.1", + "@emotion/utils": "^1.2.1", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" + }, + "node_modules/@emotion/styled": { + "version": "11.11.5", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.5.tgz", + "integrity": "sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/is-prop-valid": "^1.2.2", + "@emotion/serialize": "^1.1.4", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", + "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -2381,6 +2547,52 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.1.tgz", + "integrity": "sha512-GkWzv+L6d2bI5f/Vk6ikJ9xtl7dfXtoRu3YGE6nq0p/FFqA1ebMOAWg3XgRyb0I6LYyYkiAo+3/KrwuBp8xG7A==", + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.1.tgz", + "integrity": "sha512-MfRCYlQPXoLlpem+egxjfkEuP9UQswTrlCOsknus/NcMoblTH2g0jPrapbcIb04KGA7E2GZxbAccGZfWoYgsrQ==", + "hasInstallScript": true, + "peer": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.5.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.1.tgz", + "integrity": "sha512-S1PPfU3mIJa59biTtXJz1oI0+KAXW6bkAb31XKhxdxtuXDiUIFsih4JR1v5BbxY7hVHsD1RKq+jRkVRaf773NQ==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.5.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/react-fontawesome": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz", + "integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.3" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -3338,6 +3550,23 @@ } } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@remix-run/router": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", + "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -5223,6 +5452,29 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "dependencies": { + "follow-redirects": "^1.15.6", + "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.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", @@ -5923,6 +6175,11 @@ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==" }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, "node_modules/clean-css": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", @@ -8274,6 +8531,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", @@ -8308,9 +8570,9 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==" }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -8938,6 +9200,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", @@ -14522,6 +14797,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", @@ -14805,11 +15085,41 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, + "node_modules/react-hook-form": { + "version": "7.51.2", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.2.tgz", + "integrity": "sha512-y++lwaWjtzDt/XNnyGDQy6goHskFualmDlf+jzEZvjvz6KWDf7EboL7pUvRCzPTJd0EOPpdekYaQLEvvG6m6HA==", + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/react-pro-sidebar": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/react-pro-sidebar/-/react-pro-sidebar-1.1.0.tgz", + "integrity": "sha512-rdRJ4PeMsqWq9n69AmF6et6qCbhCF1KEBgjAH8vIiLxE1k5fMxtRYo0k4asxW8qpIH6sqahiMxrxVVoObv8orQ==", + "dependencies": { + "@emotion/react": "^11.10.5", + "@emotion/styled": "^11.10.5", + "@popperjs/core": "^2.11.6", + "classnames": "^2.3.2" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -14818,6 +15128,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", + "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", + "dependencies": { + "@remix-run/router": "1.15.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz", + "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==", + "dependencies": { + "@remix-run/router": "1.15.3", + "react-router": "6.22.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "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", @@ -16219,6 +16559,11 @@ "postcss": "^8.2.15" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", diff --git a/web/package.json b/web/package.json index 600a7e754f9e7e10c3ccd6f00eef03f0a9834790..f36efbd589ff32f2155b6d383a848966e61fb095 100644 --- a/web/package.json +++ b/web/package.json @@ -3,6 +3,8 @@ "version": "0.1.0", "private": true, "dependencies": { + "@fortawesome/free-solid-svg-icons": "^6.5.1", + "@fortawesome/react-fontawesome": "^0.2.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -10,8 +12,12 @@ "@types/node": "^16.18.87", "@types/react": "^18.2.64", "@types/react-dom": "^18.2.21", + "axios": "^1.6.8", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hook-form": "^7.51.2", + "react-pro-sidebar": "^1.1.0", + "react-router-dom": "^6.22.3", "react-scripts": "5.0.1", "typescript": "^4.9.5", "web-vitals": "^2.1.4" @@ -39,5 +45,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "tailwindcss": "^3.4.1" } } diff --git a/web/src/App.tsx b/web/src/App.tsx index a53698aab3c66049c61980112dd0109dd2cd0845..8dff12f1d12ddbbf9d2a12ea5179b98385885076 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,24 +1,18 @@ -import React from 'react'; -import logo from './logo.svg'; +import { RouterProvider } from 'react-router-dom'; import './App.css'; +import { AuthContextProvider } from './context/auth_context'; +import { MessageContextProvider } from './context/message_context'; +import { router } from './router/router'; + function App() { return (
-
- logo -

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

- - Learn React - -
+ + + + +
); } diff --git a/web/src/components/admin_panel_navbar/admin_navbar.tsx b/web/src/components/admin_panel_navbar/admin_navbar.tsx new file mode 100644 index 0000000000000000000000000000000000000000..424bd0633d14451ce363068a75b14018fdda6fbf --- /dev/null +++ b/web/src/components/admin_panel_navbar/admin_navbar.tsx @@ -0,0 +1,45 @@ +import { useState } from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faSignOut, faUser } from "@fortawesome/free-solid-svg-icons"; +import { useAuth } from "../../context/auth_context"; +import { Link } from "react-router-dom"; +import './assets/styles/style.css'; + +export const AdminPanelNavBar = () => { + const {user, logout} = useAuth(); + const [toggle, setToggle] = useState(false); + + const handleLogout = () => { + logout(); + }; + + return ( +
+
+ setToggle(!toggle)}/> + {toggle && +
+
+
+ +

Superadmin

+
+
+ + + +

Editar cuenta

+ + + + +

Cerrar sesión

+ +
+
+ } +
+
+ ); +} \ No newline at end of file diff --git a/web/src/components/admin_panel_navbar/assets/images/Admin-595b40b65ba036ed117d36fe.png b/web/src/components/admin_panel_navbar/assets/images/Admin-595b40b65ba036ed117d36fe.png new file mode 100644 index 0000000000000000000000000000000000000000..f2e44e0c59cb9d29b25a8c922f4ea1fdc3cab308 Binary files /dev/null and b/web/src/components/admin_panel_navbar/assets/images/Admin-595b40b65ba036ed117d36fe.png differ diff --git a/web/src/components/admin_panel_navbar/assets/styles/style.css b/web/src/components/admin_panel_navbar/assets/styles/style.css new file mode 100644 index 0000000000000000000000000000000000000000..0762a6709c9e7e0d467a5cf68bd373d6b71dfb5c --- /dev/null +++ b/web/src/components/admin_panel_navbar/assets/styles/style.css @@ -0,0 +1,90 @@ +:root { + --shadow: 0px 2px 8px 0px gray; +} + +.navbar{ + height: 40px; + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + box-shadow: var(--shadow); + +} + +.profile{ + position: absolute; + right: 20px; +} + +.user-pic{ + width: 30px; + border-radius: 50%; + cursor: pointer; + background: white; +} + +.profile-sub-menu-wrap{ + position: absolute; + top: 100%; + right: 10%; + max-width: 400px; + overflow: hidden; + transition: max-height 0.5s; +} + +.sub-menu{ + background: white; + padding: 10px; + margin: 10px; +} + +.user-info{ + display: flex; + align-items: center; +} + +.user-info img{ + width: 60px; + margin-right: 15px; + border-radius: 50%; +} + +.user-info h3{ + font-weight: 500; +} + +.sub-menu hr{ + border: 0; + height: 1px; + width: 100%; + background: #ccc ; + margin: 15px 0 10px; +} + +.sub-menu-link{ + display: flex; + align-items: center; + text-decoration: none; + color: #525252; +} + +.sub-menu-link:hover .sub-menu-link-icon{ + transform: scale(1.1); +} + +.sub-menu-link:hover p{ + font-weight: 600; +} + +.sub-menu-link p{ + user-select: none; +} + +.sub-menu-link .sub-menu-link-icon{ + width: 20px; + background: #E5E5E5; + border-radius: 50%; + padding: 8px; + margin-right: 15px; +} \ No newline at end of file diff --git a/web/src/components/sa_panel_admin/sa_panel_admin_screen/sa_panel_admin_screen.tsx b/web/src/components/sa_panel_admin/sa_panel_admin_screen/sa_panel_admin_screen.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b91f596188474ba0831d775a137fc84fbb862ac0 --- /dev/null +++ b/web/src/components/sa_panel_admin/sa_panel_admin_screen/sa_panel_admin_screen.tsx @@ -0,0 +1,8 @@ + +export const SuperadminPanelAdminScreen = () => { + return ( +
+ +
+ ); +} \ No newline at end of file diff --git a/web/src/components/sa_panel_town/sa_panel_town_screen/css/styles.css b/web/src/components/sa_panel_town/sa_panel_town_screen/css/styles.css new file mode 100644 index 0000000000000000000000000000000000000000..2713080dda323b253a843a595638d986d34072a9 --- /dev/null +++ b/web/src/components/sa_panel_town/sa_panel_town_screen/css/styles.css @@ -0,0 +1,20 @@ +.sa_panel_town_content{ + background: gray; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; +} + +.town_panel_header{ + display: flex; + width: 100%; + align-items: center; + justify-content: center; +} + +.town_add_btn{ + position: absolute; + top: 50%; + right: 5px; +} \ No newline at end of file diff --git a/web/src/components/sa_panel_town/sa_panel_town_screen/sa_panel_town_screen.tsx b/web/src/components/sa_panel_town/sa_panel_town_screen/sa_panel_town_screen.tsx new file mode 100644 index 0000000000000000000000000000000000000000..dc7bab0f97a32cc17ce7c26352c254d2283f7e46 --- /dev/null +++ b/web/src/components/sa_panel_town/sa_panel_town_screen/sa_panel_town_screen.tsx @@ -0,0 +1,15 @@ +import './css/styles.css' + +export const SuperadminPanelTownScreen = () => { + return ( +
+
+ Administrar pueblos mágicos +
+
+ +
+
+ ); +} \ No newline at end of file diff --git a/web/src/components/sidebar_header/assets/css/styles.css b/web/src/components/sidebar_header/assets/css/styles.css new file mode 100644 index 0000000000000000000000000000000000000000..f47166b7c4d5fa5d20181f23a77c6be4905e9d06 --- /dev/null +++ b/web/src/components/sidebar_header/assets/css/styles.css @@ -0,0 +1,11 @@ +.sidebar_header { + height: 64px; + min-height: 64px; + display: flex; + align-items: center; + padding: 0 20px; +} + +.sidebar_header_content{ + +} \ No newline at end of file diff --git a/web/src/components/sidebar_header/sidebar_header.tsx b/web/src/components/sidebar_header/sidebar_header.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f23c496b1fe84e6e2c2b3fd14bd9ddc697deb734 --- /dev/null +++ b/web/src/components/sidebar_header/sidebar_header.tsx @@ -0,0 +1,18 @@ +import { UserRole } from '../../constants/roles'; +import { useAuth } from '../../context/auth_context'; +import './assets/css/styles.css'; + +export const SidebarHeader = () => { + const {user} = useAuth(); + if(!user){ + return null; + } + + return ( +
+
+

{user.role==UserRole.SUPERADMIN ? 'Superadmin': 'Admin'} Panel

+
+
+ ); +} \ No newline at end of file diff --git a/web/src/constants/api_url.ts b/web/src/constants/api_url.ts new file mode 100644 index 0000000000000000000000000000000000000000..b712d33928a6ffa71f9619a88dfe2a43330182a0 --- /dev/null +++ b/web/src/constants/api_url.ts @@ -0,0 +1 @@ +export const APIUrl: String = "http://localhost:3005"; \ No newline at end of file diff --git a/web/src/constants/roles.ts b/web/src/constants/roles.ts new file mode 100644 index 0000000000000000000000000000000000000000..02cddf704502cf502565b1bb76d13ecee2d0b2b2 --- /dev/null +++ b/web/src/constants/roles.ts @@ -0,0 +1,7 @@ +export enum UserRole { + ADMIN = "admin", + SUPERADMIN = "superadmin", +} + +export const SUPERADMIN_ROLES = [UserRole.SUPERADMIN]; +export const ADMIN_ROLES = [UserRole.ADMIN, UserRole.SUPERADMIN]; \ No newline at end of file diff --git a/web/src/context/auth_context.tsx b/web/src/context/auth_context.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2648700def5b1f352a261097a596cfdd34c1e7b5 --- /dev/null +++ b/web/src/context/auth_context.tsx @@ -0,0 +1,68 @@ +import { UserEntity } from "../infraestructure/entities/user"; +import axios from "axios"; +import { UserRole } from "../constants/roles"; +import { ReactNode, createContext, useContext, useEffect, useState } from "react"; + +type AuthContextType = { + user: UserEntity | null; + login: (user: UserEntity, token: string) => void; + logout: () => void; +}; + +const AuthContext = createContext({ + user: null, + login: () => {}, + logout: () => {} +}); + +type AuthContextProviderProps = { + children: ReactNode; +} + +export const AuthContextProvider = ({children} : AuthContextProviderProps) => { + const [user, setUser] = useState(null); + const saveSession = async (user: UserEntity, token: string) => { + setUser(user); + localStorage.setItem("token",token); + localStorage.setItem("user",JSON.stringify(user)); + axios.defaults.headers.common["Authorization"] = `${token}`; + } + + const deleteSession = async () => { + setUser(null); + localStorage.removeItem("token"); + localStorage.removeItem("user"); + axios.defaults.headers.common["Authorization"] = ""; + } + + const login = async (user: UserEntity, token: string) => { + await saveSession(user, token); + } + + const logout = async () => { + await deleteSession(); + } + + const checkSession = async () => { + const token = localStorage.getItem("token"); + const user = localStorage.getItem("user"); + if(token && user){ + const sessionUser = JSON.parse(user); + await saveSession({email: sessionUser.email as string, role: sessionUser.role as UserRole}, token); + } + } + + useEffect(() => { + checkSession(); + }, []); + + const value = {user, login, logout}; + return {children} +}; + +export const useAuth = () => { + if(AuthContext == undefined){ + throw new Error("useAuth debe se usado dentro de un AuthContextProvider"); + } + return useContext(AuthContext); +}; \ No newline at end of file diff --git a/web/src/context/message_context.tsx b/web/src/context/message_context.tsx new file mode 100644 index 0000000000000000000000000000000000000000..1ce309bec77a5cb93eb182b374430ac90cb8e8bd --- /dev/null +++ b/web/src/context/message_context.tsx @@ -0,0 +1,47 @@ +import { createContext, ReactNode, useContext, useState } from "react"; + +type MessageContextType = { + isVisible: boolean; + message: string; + showMessage: (message: string) => void; + hideMessage: () => void; +} + +const MessageContext = createContext({ + isVisible: false, + message: "", + showMessage: () => {}, + hideMessage: () => {} +}); + +type MessageContextProviderProps = { + children: ReactNode; +} + +export const MessageContextProvider = ({children}: MessageContextProviderProps) => { + const [isVisible, setIsVisible] = useState(false); + const [message, setMessage] = useState(""); + const showMessage = (message: string) => { + setIsVisible(true); + setMessage(message); + setTimeout(() => { + setIsVisible(false); + setMessage(""); + }, 3000); + }; + + const hideMessage = () => { + setIsVisible(false); + setMessage(""); + }; + + const value = {isVisible, message, showMessage, hideMessage}; + return {children} +} + +export const useMessage = () => { + if(MessageContext == undefined){ + throw new Error("useMessage debe se usado dentro de un MessageContextProvider"); + } + return useContext(MessageContext); +}; \ No newline at end of file diff --git a/web/src/data/datasources/prod/login_datasource.ts b/web/src/data/datasources/prod/login_datasource.ts new file mode 100644 index 0000000000000000000000000000000000000000..c2d7f535a1381fe04aef54278c4b8ce15fb32ed3 --- /dev/null +++ b/web/src/data/datasources/prod/login_datasource.ts @@ -0,0 +1,30 @@ +import { LoginDatasourceInf } from "../../../infraestructure/datasources/login_datasource"; +import { LoginFormValues } from "../../../infraestructure/entities/login_form_values"; +import axios from "axios"; +import { LoggedInUser } from "../../../infraestructure/entities/user"; +import { LoggedInUserModel } from "../../models/prod/LoggedInUserModel"; +import { UserRole } from "../../../constants/roles"; +import { APIUrl } from "../../../constants/api_url"; + +export class LoginDatasourceProd implements LoginDatasourceInf{ + async getToken(form: LoginFormValues): Promise { + const {email, password} = form; + const {data} = await axios.post( + APIUrl + "/admin/signin", + { + email: email, + password: password, + } + ); + const user: LoggedInUser = { + user: { + email: data.email, + name: data.name, + role: data.role === UserRole.SUPERADMIN ? UserRole.SUPERADMIN : UserRole.ADMIN, + }, + token: data.token, + }; + + return user; + } +} \ No newline at end of file diff --git a/web/src/data/models/prod/LoggedInUserModel.ts b/web/src/data/models/prod/LoggedInUserModel.ts new file mode 100644 index 0000000000000000000000000000000000000000..8f73d45b187796cdfa6ae2c154a6d1b5a252db0c --- /dev/null +++ b/web/src/data/models/prod/LoggedInUserModel.ts @@ -0,0 +1,6 @@ +export interface LoggedInUserModel { + email: string; + name: string; + role: string; + token: string; +} \ No newline at end of file diff --git a/web/src/data/repositories/prod/login_repository.ts b/web/src/data/repositories/prod/login_repository.ts new file mode 100644 index 0000000000000000000000000000000000000000..69c0694fa4ea1ac50aa3890d7c8aedc305f364c1 --- /dev/null +++ b/web/src/data/repositories/prod/login_repository.ts @@ -0,0 +1,13 @@ +import { LoginDatasourceInf } from "../../../infraestructure/datasources/login_datasource"; +import { LoginFormValues } from "../../../infraestructure/entities/login_form_values"; +import { LoggedInUser } from "../../../infraestructure/entities/user"; +import { LoginRepositoryInf } from "../../../infraestructure/repositories/login_repository"; + +export class LoginRepositoryProd implements LoginRepositoryInf{ + constructor( + private datasource: LoginDatasourceInf + ){} + async getToken(form: LoginFormValues): Promise { + return this.datasource.getToken(form); + } +} \ No newline at end of file diff --git a/web/src/hooks/useLogin.tsx b/web/src/hooks/useLogin.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6a8ec16917269db00aac523e77ea0761715b1cba --- /dev/null +++ b/web/src/hooks/useLogin.tsx @@ -0,0 +1,41 @@ +import { SubmitHandler, useForm } from "react-hook-form" +import { LoginFormValues } from "../infraestructure/entities/login_form_values" +import { LoginRepositoryProd } from "../data/repositories/prod/login_repository"; +import { LoginDatasourceProd } from "../data/datasources/prod/login_datasource"; +import { useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { useAuth } from "../context/auth_context"; +const loginDatasource = new LoginDatasourceProd(); +const loginRepository = new LoginRepositoryProd(loginDatasource); + +export const useLogin = () => { + const {login, user} = useAuth(); + const navigate = useNavigate(); + const { + register, + handleSubmit, + setError, + formState: {errors}, + } = useForm(); + + const onSubmit: SubmitHandler = (data: LoginFormValues) => { + const authenticate = async () => { + try{ + const {user, token } = await loginRepository.getToken(data); + await login(user, token); + navigate("/"); + }catch(error: any){ + + } + } + authenticate(); + } + + useEffect(() => { + if(user) { + navigate("/"); + } + }, []); + + return { register, handleSubmit, onSubmit, errors }; +} \ No newline at end of file diff --git a/web/src/hooks/usePasswordVisibility.tsx b/web/src/hooks/usePasswordVisibility.tsx new file mode 100644 index 0000000000000000000000000000000000000000..23108a824cab3a048531926308d79f2c05edafc4 --- /dev/null +++ b/web/src/hooks/usePasswordVisibility.tsx @@ -0,0 +1,20 @@ +import { useState } from "react" + +export const usePasswoordVisibility = () => { + const [values, setValues] = useState({ + showPassword: false, + }); + + const handleClickShowPassword = () => { + setValues({ + ...values, + showPassword: !values.showPassword, + }); + }; + + const handleMouseDownPassword = (event: any) => { + event.preventDefault(); + }; + + return {values, handleClickShowPassword, handleMouseDownPassword}; +} \ No newline at end of file diff --git a/web/src/infraestructure/datasources/login_datasource.ts b/web/src/infraestructure/datasources/login_datasource.ts new file mode 100644 index 0000000000000000000000000000000000000000..762def1d77f5e3db577ea99cb2f7e913d414ba8e --- /dev/null +++ b/web/src/infraestructure/datasources/login_datasource.ts @@ -0,0 +1,6 @@ +import { LoginFormValues } from "../entities/login_form_values"; +import { LoggedInUser } from "../entities/user"; + +export interface LoginDatasourceInf { + getToken(form: LoginFormValues): Promise; +} diff --git a/web/src/infraestructure/entities/login_form_values.ts b/web/src/infraestructure/entities/login_form_values.ts new file mode 100644 index 0000000000000000000000000000000000000000..78456b1e9ed6c071fce5c8e5ffcca45d45472772 --- /dev/null +++ b/web/src/infraestructure/entities/login_form_values.ts @@ -0,0 +1,4 @@ +export interface LoginFormValues { + email : string + password : string +}; \ No newline at end of file diff --git a/web/src/infraestructure/entities/user.ts b/web/src/infraestructure/entities/user.ts new file mode 100644 index 0000000000000000000000000000000000000000..a600d77e58cefc74ca065fc6513610a2b847e47f --- /dev/null +++ b/web/src/infraestructure/entities/user.ts @@ -0,0 +1,19 @@ +import { UserRole } from "../../constants/roles"; + +export interface UserEntity { + name?: string; + lastName?: string; + email: string; + role: UserRole; +} + +export interface LoggedInUser { + user: UserEntity; + token: string; +} + +export interface UserInfo { + name?: string; + lastName?: string; + email: string; +} \ No newline at end of file diff --git a/web/src/infraestructure/repositories/login_repository.ts b/web/src/infraestructure/repositories/login_repository.ts new file mode 100644 index 0000000000000000000000000000000000000000..886c8eccdce3ccda29962845b14f68560cc78802 --- /dev/null +++ b/web/src/infraestructure/repositories/login_repository.ts @@ -0,0 +1,6 @@ +import { LoginFormValues } from "../entities/login_form_values"; +import { LoggedInUser } from "../entities/user"; + +export interface LoginRepositoryInf{ + getToken(form: LoginFormValues): Promise; +} \ No newline at end of file diff --git a/web/src/pages/home/super_admin_page/assets/styles/style.css b/web/src/pages/home/super_admin_page/assets/styles/style.css new file mode 100644 index 0000000000000000000000000000000000000000..3ccfed0bd768c9376c8187c9dc0d7035396b9562 --- /dev/null +++ b/web/src/pages/home/super_admin_page/assets/styles/style.css @@ -0,0 +1,19 @@ +:root { + --shadow: 0px 2px 8px 0px gray; +} + + +.superdmin-panel-root{ + display: flex; + height: 100%; + width: 100%; +} + + +.superadmin-panel-body{ + height: 100vh; + flex-grow: 1; + display: flex; + flex-direction: column; + background: #ECEAFF; +} diff --git a/web/src/pages/home/super_admin_page/super_admin_home_page.tsx b/web/src/pages/home/super_admin_page/super_admin_home_page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9c6d16fa5d76cebaa584699c42590c6d0c0c65db --- /dev/null +++ b/web/src/pages/home/super_admin_page/super_admin_home_page.tsx @@ -0,0 +1,41 @@ +import { Menu, MenuItem, Sidebar } from "react-pro-sidebar" +import './assets/styles/style.css'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faMonument, faSignOut, faUser, faUserTie } from "@fortawesome/free-solid-svg-icons"; +import { useState } from "react"; +import { useAuth } from "../../../context/auth_context"; +import { Link } from "react-router-dom"; +import { AdminPanelNavBar } from "../../../components/admin_panel_navbar/admin_navbar"; +import { SidebarHeader } from "../../../components/sidebar_header/sidebar_header"; +import { SuperadminPanelTownScreen } from "../../../components/sa_panel_town/sa_panel_town_screen/sa_panel_town_screen"; +import { SuperadminPanelAdminScreen } from "../../../components/sa_panel_admin/sa_panel_admin_screen/sa_panel_admin_screen"; + +export const SuperAdminHomePage = () => { + const [collapsed, setCollapsed] = useState(true); + const [townPanel, setTownPanel] = useState(true); + + return ( +
+ setCollapsed(false)} + onMouseOut={() => setCollapsed(true)}> + + + } onClick={() => setTownPanel(true)}> + Pueblos + + + } onClick={() => setTownPanel(false)}> + Administradores + + + +
+ +
+ {townPanel ? : } +
+
+
+ ) +} \ No newline at end of file diff --git a/web/src/pages/login_page/login_page.tsx b/web/src/pages/login_page/login_page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6cbc8ce23e8be06cd73e1c18f072edeef7a7b4ab --- /dev/null +++ b/web/src/pages/login_page/login_page.tsx @@ -0,0 +1,60 @@ +import { usePasswoordVisibility } from "../../hooks/usePasswordVisibility" +import "./styles/styles.css" +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faEye , faEyeSlash} from "@fortawesome/free-solid-svg-icons"; +import { useLogin } from "../../hooks/useLogin"; + +export const LoginPage = () => { + const { + values, + handleClickShowPassword, + handleMouseDownPassword, + } = usePasswoordVisibility(); + + const { + register, + handleSubmit, + errors, + onSubmit + } = useLogin(); + + return ( +
+
+
+ Inicia sesión +
+
+
+ + +
+
+ + + +
+
+ +
+
+
+
+ ) +} \ No newline at end of file diff --git a/web/src/pages/login_page/styles/styles.css b/web/src/pages/login_page/styles/styles.css new file mode 100644 index 0000000000000000000000000000000000000000..1d17cb075be9456b0ec4123e256e7057ca4e6116 --- /dev/null +++ b/web/src/pages/login_page/styles/styles.css @@ -0,0 +1,122 @@ +@import url('https://fonts.googleapis.com/css?family=Poppins:400,500,600,700&display=swap'); + +* { + margin: 10; + padding: 0; + font-family: 'Poppins', sans-serif; + box-sizing: border-box; +} + +.login-page-root{ + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + width: 100vw; +} + +.login-form-container{ + border-radius: 15px; + background: white; + display: grid; + place-items: center; + box-shadow: 0px 15px 20px rgba(0,0,0,0.1); +} + +.login-form-container .title{ + font-size: 35px; + font-weight: 600; + width: 100%; + color: white; + line-height: 50px; + text-align: center; + user-select: none; + border-radius: 15px 15px 0 0; + background: linear-gradient(-140deg,#413d3d, rgb(0, 0, 0)) +} + +.login-form-container form{ + padding: 10px 40px 50px 30px; +} + +.login-form-container .login-form-field{ + position: relative; + height: 50px; + width: 100%; + margin-top: 20px; +} + +.login-form-container form .login-form-field input{ + height: 100%; + width: 100%; + outline: none; + font-size: 17px; + padding-left: 20px; + padding-right: 30px; + border: 1px solid lightgray; + border-radius: 15px; + transition: all 0.3s ease; +} + +.login-form-container form .login-form-field input:focus, +form .login-form-field input:valid{ + border-color: black; +} + +.login-form-container form .login-form-field label{ + position: absolute; + top: 50%; + left: 20px; + color: #999999; + font-weight: 400; + font-size: 17px; + pointer-events: none; + transform: all 0.3s ease; + transform: translateY(-50%); +} + +.login-form-container form .login-form-field .pass-visibility-button{ + position: absolute; + top: 50%; + right: 5px; + transform: translateY(-50%); + transform: all 0.3s ease; + cursor: pointer; +} + + +form .login-form-field input:focus ~ label, +form .login-form-field input:valid ~ label, +form .login-form-field input[type="email"]:not(:placeholder-shown) ~ label, +form .login-form-field input[type="email"]:focus ~ label { + top: 0%; + color: black; + font-size: 16px; + background-color: white; + transform: translateY(-50%); +} + +.login-form-container form .login-form-button { + height: 50px; + width: 100%; + margin-top: 20px; + position: relative; +} + +form .login-form-button input{ + color: white; + border: none; + padding-left: 0; + margin-top: -10px; + font-size: 20px; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + background: black; + width: 100%; + border-radius: 5px; +} + +form .login-form-button input:active { + transform: scale(0.95); +} \ No newline at end of file diff --git a/web/src/router/login_route.tsx b/web/src/router/login_route.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b401a7fb41f3f76da56fbcd4757275130fa23e10 --- /dev/null +++ b/web/src/router/login_route.tsx @@ -0,0 +1,10 @@ +import { Navigate, Outlet } from "react-router-dom"; +import { useAuth } from "../context/auth_context"; + +export const LoginRoute = () => { + const {user} = useAuth(); + if(user){ + return ; + } + return ; +} \ No newline at end of file diff --git a/web/src/router/protected_route.tsx b/web/src/router/protected_route.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2a412f384bcbe251c3bdef958253be25d3318984 --- /dev/null +++ b/web/src/router/protected_route.tsx @@ -0,0 +1,15 @@ +import { Navigate, Outlet } from "react-router-dom"; +import { useAuth } from "../context/auth_context"; + +interface ProtectedRouteProps { + allowedRoles?: string[]; +} + +export const ProtectedRoute = ({allowedRoles}: ProtectedRouteProps) => { + const {user} = useAuth(); + if(!user){ + return ; + } + + return ; +} \ No newline at end of file diff --git a/web/src/router/router.tsx b/web/src/router/router.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9e051b3af36d2dadde7c7e533ad005fa60446b03 --- /dev/null +++ b/web/src/router/router.tsx @@ -0,0 +1,27 @@ +import { createBrowserRouter } from "react-router-dom"; +import { SUPERADMIN_ROLES, ADMIN_ROLES } from "../constants/roles"; +import { LoginPage } from "../pages/login_page/login_page"; +import { ProtectedRoute } from "./protected_route"; +import { SuperAdminHomePage } from "../pages/home/super_admin_page/super_admin_home_page"; + +export const router = createBrowserRouter([ + { + element: , + children: [ + { + index: true, + path: "/", + element: + } + ] + }, + { + element: , + children: [ + { + path: "/login", + element: + } + ] + } +]); \ No newline at end of file