diff --git a/web/package-lock.json b/web/package-lock.json
index dd8e9e47bb7a6a9d80a02df029dbc72644a66fd1..efcf50016a199d770751159e26fa77f4820905ac 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -10,6 +10,8 @@
"dependencies": {
"@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/react-fontawesome": "^0.2.0",
+ "@hookform/resolvers": "^3.3.4",
+ "@react-google-maps/api": "^2.19.3",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
@@ -19,6 +21,7 @@
"@types/react-dom": "^18.2.21",
"axios": "^1.6.8",
"react": "^18.2.0",
+ "react-data-table-component": "^7.6.2",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-hook-form": "^7.51.2",
@@ -27,7 +30,8 @@
"react-scripts": "5.0.1",
"react-toastify": "^10.0.5",
"typescript": "^4.9.5",
- "web-vitals": "^2.1.4"
+ "web-vitals": "^2.1.4",
+ "yup": "^1.4.0"
},
"devDependencies": {
"tailwindcss": "^3.4.1"
@@ -2595,6 +2599,31 @@
"react": ">=16.3"
}
},
+ "node_modules/@googlemaps/js-api-loader": {
+ "version": "1.16.2",
+ "resolved": "https://registry.npmjs.org/@googlemaps/js-api-loader/-/js-api-loader-1.16.2.tgz",
+ "integrity": "sha512-psGw5u0QM6humao48Hn4lrChOM2/rA43ZCm3tKK9qQsEj1/VzqkCqnvGfEOshDbBQflydfaRovbKwZMF4AyqbA==",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3"
+ }
+ },
+ "node_modules/@googlemaps/markerclusterer": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/@googlemaps/markerclusterer/-/markerclusterer-2.5.3.tgz",
+ "integrity": "sha512-x7lX0R5yYOoiNectr10wLgCBasNcXFHiADIBdmn7jQllF2B5ENQw5XtZK+hIw4xnV0Df0xhN4LN98XqA5jaiOw==",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "supercluster": "^8.0.1"
+ }
+ },
+ "node_modules/@hookform/resolvers": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.3.4.tgz",
+ "integrity": "sha512-o5cgpGOuJYrd+iMKvkttOclgwRW86EsWJZZRC23prf0uU2i48Htq4PuT73AVb9ionFyZrwYEITuOFGF+BydEtQ==",
+ "peerDependencies": {
+ "react-hook-form": "^7.0.0"
+ }
+ },
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
@@ -3561,6 +3590,33 @@
"url": "https://opencollective.com/popperjs"
}
},
+ "node_modules/@react-google-maps/api": {
+ "version": "2.19.3",
+ "resolved": "https://registry.npmjs.org/@react-google-maps/api/-/api-2.19.3.tgz",
+ "integrity": "sha512-jiLqvuOt5lOowkLeq7d077AByTyJp+s6hZVlLhlq7SBacBD37aUNpXBz2OsazfeR6Aw4a+9RRhAEjEFvrR1f5A==",
+ "dependencies": {
+ "@googlemaps/js-api-loader": "1.16.2",
+ "@googlemaps/markerclusterer": "2.5.3",
+ "@react-google-maps/infobox": "2.19.2",
+ "@react-google-maps/marker-clusterer": "2.19.2",
+ "@types/google.maps": "3.55.2",
+ "invariant": "2.2.4"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17 || ^18",
+ "react-dom": "^16.8 || ^17 || ^18"
+ }
+ },
+ "node_modules/@react-google-maps/infobox": {
+ "version": "2.19.2",
+ "resolved": "https://registry.npmjs.org/@react-google-maps/infobox/-/infobox-2.19.2.tgz",
+ "integrity": "sha512-6wvBqeJsQ/eFSvoxg+9VoncQvNoVCdmxzxRpLvmjPD+nNC6mHM0vJH1xSqaKijkMrfLJT0nfkTGpovrF896jwg=="
+ },
+ "node_modules/@react-google-maps/marker-clusterer": {
+ "version": "2.19.2",
+ "resolved": "https://registry.npmjs.org/@react-google-maps/marker-clusterer/-/marker-clusterer-2.19.2.tgz",
+ "integrity": "sha512-x9ibmsP0ZVqzyCo1Pitbw+4b6iEXRw/r1TCy3vOUR3eKrzWLnHYZMR325BkZW2r8fnuWE/V3Fp4QZOP9qYORCw=="
+ },
"node_modules/@remix-run/router": {
"version": "1.15.3",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz",
@@ -4326,6 +4382,11 @@
"@types/send": "*"
}
},
+ "node_modules/@types/google.maps": {
+ "version": "3.55.2",
+ "resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.55.2.tgz",
+ "integrity": "sha512-JcTwzkxskR8DN/nnX96Pie3gGN3WHiPpuxzuQ9z3516o1bB243d8w8DHUJ8BohuzoT1o3HUFta2ns/mkZC8KRw=="
+ },
"node_modules/@types/graceful-fs": {
"version": "4.1.9",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
@@ -4521,6 +4582,12 @@
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
"integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="
},
+ "node_modules/@types/stylis": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz",
+ "integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==",
+ "peer": true
+ },
"node_modules/@types/testing-library__jest-dom": {
"version": "5.14.9",
"resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz",
@@ -6060,6 +6127,15 @@
"node": ">= 6"
}
},
+ "node_modules/camelize": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
+ "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
+ "peer": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/caniuse-api": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
@@ -6505,6 +6581,15 @@
"postcss": "^8.4"
}
},
+ "node_modules/css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
+ "peer": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/css-declaration-sorter": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz",
@@ -6695,6 +6780,17 @@
"resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz",
"integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w=="
},
+ "node_modules/css-to-react-native": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
+ "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
+ "peer": true,
+ "dependencies": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
"node_modules/css-tree": {
"version": "1.0.0-alpha.37",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz",
@@ -9631,6 +9727,14 @@
"node": ">= 0.4"
}
},
+ "node_modules/invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"node_modules/ipaddr.js": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz",
@@ -12348,6 +12452,11 @@
"node": ">=4.0"
}
},
+ "node_modules/kdbush": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz",
+ "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA=="
+ },
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -14806,6 +14915,11 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
+ "node_modules/property-expr": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz",
+ "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA=="
+ },
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -14980,6 +15094,23 @@
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
+ "node_modules/react-data-table-component": {
+ "version": "7.6.2",
+ "resolved": "https://registry.npmjs.org/react-data-table-component/-/react-data-table-component-7.6.2.tgz",
+ "integrity": "sha512-nHe7040fmtrJyQr/ieGrTfV0jBflYGK4sLokC6/AFOv3ThjmA9WzKz8Z8/2wMxzRqLU+Rn0CVFg+8+frKLepWQ==",
+ "dependencies": {
+ "deepmerge": "^4.3.1"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.3",
+ "styled-components": ">= 5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "styled-components": {
+ "optional": false
+ }
+ }
+ },
"node_modules/react-dev-utils": {
"version": "12.0.1",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",
@@ -16070,6 +16201,12 @@
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
},
+ "node_modules/shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
+ "peer": true
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -16601,6 +16738,89 @@
"webpack": "^5.0.0"
}
},
+ "node_modules/styled-components": {
+ "version": "6.1.9",
+ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.9.tgz",
+ "integrity": "sha512-aBOqs0uMsYufFXSE4q6cA6Ty1fwZuMk4BJRHfiGSna59F1otnxiDelwhN4fEwmBtIymmF0ZqXHnpSigr2ps9Cg==",
+ "peer": true,
+ "dependencies": {
+ "@emotion/is-prop-valid": "1.2.1",
+ "@emotion/unitless": "0.8.1",
+ "@types/stylis": "4.2.0",
+ "css-to-react-native": "3.2.0",
+ "csstype": "3.1.2",
+ "postcss": "8.4.31",
+ "shallowequal": "1.1.0",
+ "stylis": "4.3.1",
+ "tslib": "2.5.0"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/styled-components"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0",
+ "react-dom": ">= 16.8.0"
+ }
+ },
+ "node_modules/styled-components/node_modules/@emotion/is-prop-valid": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz",
+ "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==",
+ "peer": true,
+ "dependencies": {
+ "@emotion/memoize": "^0.8.1"
+ }
+ },
+ "node_modules/styled-components/node_modules/csstype": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
+ "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
+ "peer": true
+ },
+ "node_modules/styled-components/node_modules/postcss": {
+ "version": "8.4.31",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "peer": true,
+ "dependencies": {
+ "nanoid": "^3.3.6",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/styled-components/node_modules/stylis": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz",
+ "integrity": "sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ==",
+ "peer": true
+ },
+ "node_modules/styled-components/node_modules/tslib": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
+ "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==",
+ "peer": true
+ },
"node_modules/stylehacks": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz",
@@ -16693,6 +16913,14 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/supercluster": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz",
+ "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==",
+ "dependencies": {
+ "kdbush": "^4.0.2"
+ }
+ },
"node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@@ -17033,6 +17261,11 @@
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="
},
+ "node_modules/tiny-case": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz",
+ "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q=="
+ },
"node_modules/tmpl": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
@@ -17065,6 +17298,11 @@
"node": ">=0.6"
}
},
+ "node_modules/toposort": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
+ "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg=="
+ },
"node_modules/tough-cookie": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
@@ -18517,6 +18755,28 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/yup": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/yup/-/yup-1.4.0.tgz",
+ "integrity": "sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==",
+ "dependencies": {
+ "property-expr": "^2.0.5",
+ "tiny-case": "^1.0.3",
+ "toposort": "^2.0.2",
+ "type-fest": "^2.19.0"
+ }
+ },
+ "node_modules/yup/node_modules/type-fest": {
+ "version": "2.19.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
+ "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
}
}
}
diff --git a/web/package.json b/web/package.json
index 3ac96a940a595f0fd22374d9975027b12a631ed6..f9d8a871aa57681ffc586c86e6f60ef605cbdf49 100644
--- a/web/package.json
+++ b/web/package.json
@@ -5,6 +5,8 @@
"dependencies": {
"@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/react-fontawesome": "^0.2.0",
+ "@hookform/resolvers": "^3.3.4",
+ "@react-google-maps/api": "^2.19.3",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
@@ -14,6 +16,7 @@
"@types/react-dom": "^18.2.21",
"axios": "^1.6.8",
"react": "^18.2.0",
+ "react-data-table-component": "^7.6.2",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-hook-form": "^7.51.2",
@@ -22,7 +25,8 @@
"react-scripts": "5.0.1",
"react-toastify": "^10.0.5",
"typescript": "^4.9.5",
- "web-vitals": "^2.1.4"
+ "web-vitals": "^2.1.4",
+ "yup": "^1.4.0"
},
"scripts": {
"start": "react-scripts start",
diff --git a/web/src/components/admin_panel_navbar/admin_navbar.tsx b/web/src/components/admin_panel_navbar/admin_navbar.tsx
index 6a65a776c3c60b79d224a95655c0e873160c506d..19af893ca40a6f04e77d4b45729e68fe8c47f8b4 100644
--- a/web/src/components/admin_panel_navbar/admin_navbar.tsx
+++ b/web/src/components/admin_panel_navbar/admin_navbar.tsx
@@ -1,22 +1,25 @@
-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';
+import { UserRole } from "../../constants/roles";
+import { useAdminNavbar } from "../../hooks/useAdminNavbar";
interface props{
windowActive: boolean;
}
export const AdminPanelNavBar = ({windowActive}:props) => {
- const {user, logout} = useAuth();
- const [toggle, setToggle] = useState(false);
+ const {user, handleLogout, setToggle, toggle, userData} = useAdminNavbar();
- const handleLogout = () => {
- logout();
- };
-
+ if(!user ){
+ return null;
+ }else{
+ if(user.role !== UserRole.ADMIN && user.role !== UserRole.SUPERADMIN){
+ return null;
+ }
+ }
+
return (
@@ -41,7 +44,7 @@ export const AdminPanelNavBar = ({windowActive}:props) => {
-
Superadmin
+
{userData?.name}
diff --git a/web/src/components/admin_panel_navbar/assets/styles/style.css b/web/src/components/admin_panel_navbar/assets/styles/style.css
index 7e82ea4711c167bc0a8caa4c0bc7ef3f948e2a04..6665a0775115485fbc6d3dce85551cdc4e9de414 100644
--- a/web/src/components/admin_panel_navbar/assets/styles/style.css
+++ b/web/src/components/admin_panel_navbar/assets/styles/style.css
@@ -3,13 +3,12 @@
}
.navbar{
- height: 40px;
+ height: 6vh;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: var(--shadow);
-
}
.profile{
@@ -28,7 +27,8 @@
position: absolute;
top: 100%;
right: 10%;
- max-width: 400px;
+ min-width: 300px;
+ max-width: 300px;
overflow: hidden;
z-index: 1000;
transition: max-height 0.5s;
diff --git a/web/src/components/admin_panel_places/admin_panel_place_register/admin_panel_place_register.tsx b/web/src/components/admin_panel_places/admin_panel_place_register/admin_panel_place_register.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..cbfda67fb9a980554781a3e396ee0c57504845b6
--- /dev/null
+++ b/web/src/components/admin_panel_places/admin_panel_place_register/admin_panel_place_register.tsx
@@ -0,0 +1,242 @@
+import { faWindowClose } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Dispatch, SetStateAction} from "react";
+import "./assets/css/styles.css";
+import { MapComponent } from "../../map/map";
+import { usePlaceRegister } from "../../../hooks/usePlaceRegister";
+import { languaguesList } from "../../../constants/languages";
+import { ToastContainer, toast } from "react-toastify";
+import { LoadingScreen } from "../../loading_screen/loading_screen";
+import { MultipleImagesDropzone } from "../../multiple_images_dropzone/multiple_images_dropzone";
+import { AvailableDays, availableDaysList } from "../../../infraestructure/entities/place";
+
+interface props {
+ setIsWindowActive: Dispatch
>;
+}
+
+export const AdminPanelPlaceRegister = ({setIsWindowActive}: props) => {
+ const {
+ register,
+ handleSubmit,
+ errors,
+ onSubmit,
+ setValue,
+ languageDescriptionIndexSelected,
+ descriptions,
+ setDescriptions,
+ setLanguageDescriptionIndexSelected,
+ isLoading,
+ statesList,
+ updateTownsList,
+ townsList,
+ updateTimeForm,
+ availableDays,
+ setAvailableDays,
+ resetField,
+ } = usePlaceRegister();
+
+ return (
+
+
+ Registra el lugar
+ setIsWindowActive(false)}/>
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/web/src/components/admin_panel_places/admin_panel_place_register/assets/css/styles.css b/web/src/components/admin_panel_places/admin_panel_place_register/assets/css/styles.css
new file mode 100644
index 0000000000000000000000000000000000000000..c0057a5ea8746ac93e8fe6e1b7249de51335f91c
--- /dev/null
+++ b/web/src/components/admin_panel_places/admin_panel_place_register/assets/css/styles.css
@@ -0,0 +1,134 @@
+*{
+ user-select: none;
+}
+
+.place_register_wrap{
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ margin: auto;
+ width: 75vw;
+ height: 90vh;
+ display: flex;
+ flex-direction: column;
+ background: white;
+ border: solid 2px black;
+}
+
+.place_register_header{
+ display: flex;
+ width: 100%;
+ align-items: center;
+ justify-content: center;
+ padding: 5px;
+ background: #ccc;
+ height: 7%;
+}
+
+.close_btn{
+ display: inline-block;
+ cursor: pointer;
+ height: 3%;
+ position: absolute;
+ right: 5px;
+}
+
+.place_register_body{
+ height: 93%;
+ width: 100%;
+}
+
+.place_register_body form{
+ height: 100%;
+ display: flex;
+}
+
+.inputs_container{
+ height: 100%;
+ width: 50%;
+}
+
+.map_container{
+ height: 100%;
+ width: 50%;
+ display: flex;
+ flex-direction: column;
+}
+
+.map{
+ height: 95%;
+ width: 100%;
+ padding: 10px;
+}
+
+.map_error_cnt{
+ height: 5%;
+ width: 100%;
+}
+
+.input{
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+.input_header{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+}
+
+.input .input_header select{
+ position: absolute;
+ right: 5px;
+}
+
+.error{
+ color: red;
+ font-size: 12px;
+ padding: 0;
+ margin: 0;
+}
+
+.place_description{
+ height: 130px;
+ padding: 5px;
+ overflow-y: auto;
+ resize: none;
+}
+
+.town_select_cnt{
+ display: flex;
+ width: 100%;
+}
+
+.town_select_cnt div{
+ width: 50%;
+ height: 100%;
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+}
+
+.town_select_cnt div select{
+ width: 50%;
+}
+
+.input_body{
+ display: flex;
+ width: 100%;
+ flex-direction: row;
+}
+
+.input_body div{
+ height: 100%;
+ width: 50%;
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+}
\ No newline at end of file
diff --git a/web/src/components/admin_panel_places/admin_panel_place_screen/admin_panel_place_screen.tsx b/web/src/components/admin_panel_places/admin_panel_place_screen/admin_panel_place_screen.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..f082ae26f6d77d9ce32b86b94299f8cdc0c11c9f
--- /dev/null
+++ b/web/src/components/admin_panel_places/admin_panel_place_screen/admin_panel_place_screen.tsx
@@ -0,0 +1,31 @@
+import { Dispatch, SetStateAction } from "react";
+import { AdminPanelPlaceRegister } from "../admin_panel_place_register/admin_panel_place_register";
+import "./assets/css/styles.css";
+
+interface props {
+ isWindowActive: boolean;
+ setIsWindowActive: Dispatch>;
+}
+
+export const AdminPanelPlaceScreen = ({isWindowActive,setIsWindowActive}: props) => {
+
+
+ return (
+
+
+ Administrar lugares dentro de Pueblo Mágico
+
+
+
+ {
+ isWindowActive &&
+ }
+
+
+ );
+}
\ No newline at end of file
diff --git a/web/src/components/admin_panel_places/admin_panel_place_screen/assets/css/styles.css b/web/src/components/admin_panel_places/admin_panel_place_screen/assets/css/styles.css
new file mode 100644
index 0000000000000000000000000000000000000000..1dc87adb27c563a3f58823931a9d08f831cfb00c
--- /dev/null
+++ b/web/src/components/admin_panel_places/admin_panel_place_screen/assets/css/styles.css
@@ -0,0 +1,29 @@
+.admin_panel_place_content{
+ width: 100%;
+ max-height: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+.panel_place_header{
+ height: 7%;
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 5px;
+ background: gray;
+}
+
+.panel_place_header .place_add_btn{
+ display: inline-block;
+ cursor: pointer;
+ position: absolute;
+ right: 5px;
+}
+
+.panel_place_body{
+ height: 93%;
+ width: 100%;
+ background: white;
+}
\ No newline at end of file
diff --git a/web/src/components/image_dropzone/image_dropzone.tsx b/web/src/components/image_dropzone/image_dropzone.tsx
index add3c59c09ccc42081bc1dc4eee2d42ea9d9f212..bd2101c7efc2138a735ab124cdde96deb1b32e08 100644
--- a/web/src/components/image_dropzone/image_dropzone.tsx
+++ b/web/src/components/image_dropzone/image_dropzone.tsx
@@ -4,7 +4,7 @@ import { useDropzone } from "react-dropzone";
import { useState } from 'react';
import "react-toastify/dist/ReactToastify.css";
import { UseFormSetValue } from 'react-hook-form';
-import { TownFormValues } from '../../infraestructure/entities/town_form_values';
+import { TownFormValues } from '../../infraestructure/entities/town';
interface props {
setValue : UseFormSetValue
@@ -37,7 +37,7 @@ export const ImageDropzone = ({setValue}: props) => {
})));
acceptedFiles.map((file)=>{
- {setValue('imageURL',file)}
+ {setValue('imageURL',file,{shouldValidate: true})}
});
file.onload = () => {
diff --git a/web/src/components/loading_screen/assets/css/styles.css b/web/src/components/loading_screen/assets/css/styles.css
new file mode 100644
index 0000000000000000000000000000000000000000..210b3b4139f588374b9eb8eb05bd0872ff2833ea
--- /dev/null
+++ b/web/src/components/loading_screen/assets/css/styles.css
@@ -0,0 +1,24 @@
+.loading_screen{
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.loading_spinner{
+ width: 150px;
+ padding: 20px;
+ aspect-ratio: 1;
+ border-radius: 50%;
+ background: #25b09b;
+ --_m:
+ conic-gradient(#0000 10%,#000),
+ linear-gradient(#000 0 0) content-box;
+ -webkit-mask: var(--_m);
+ mask: var(--_m);
+ -webkit-mask-composite: source-out;
+ mask-composite: subtract;
+ animation: l3 1s infinite linear;
+}
+@keyframes l3 {to{transform: rotate(1turn)}}
\ No newline at end of file
diff --git a/web/src/components/loading_screen/loading_screen.tsx b/web/src/components/loading_screen/loading_screen.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..42c8942612f160cd799753d6b769778db96520dd
--- /dev/null
+++ b/web/src/components/loading_screen/loading_screen.tsx
@@ -0,0 +1,9 @@
+import './assets/css/styles.css';
+
+export const LoadingScreen = () => {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/web/src/components/map/map.tsx b/web/src/components/map/map.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d1c45e291b7b711df9171a166197aaefc5b1ce8c
--- /dev/null
+++ b/web/src/components/map/map.tsx
@@ -0,0 +1,50 @@
+import { GoogleMap, Marker, useLoadScript } from "@react-google-maps/api";
+import { useState, useMemo } from "react";
+import { REACT_APP_GOOGLE_API_KEY } from "../../constants/api_keys";
+import { LoadingScreen } from "../loading_screen/loading_screen";
+import { UseFormSetValue } from "react-hook-form";
+import { PlaceFormValues } from "../../infraestructure/entities/place";
+
+interface props{
+ setValue: UseFormSetValue;
+}
+
+export const MapComponent = ({setValue}: props) => {
+ const {isLoaded} = useLoadScript({
+ googleMapsApiKey: REACT_APP_GOOGLE_API_KEY
+ });
+ const [marker, setMarker] = useState(false);
+ const [latitude, setLatitude] = useState(0.0);
+ const [longitude, setLongitude] = useState(0.0);
+ const updateMarker = (event: google.maps.MapMouseEvent) => {
+ if (event.latLng) {
+ setMarker(true);
+ setLatitude(event.latLng.lat());
+ setLongitude(event.latLng.lng());
+ setValue("latitude", event.latLng.lat(), {shouldValidate: true});
+ setValue("longitude", event.latLng.lng(), {shouldValidate: true});
+ }
+ }
+
+ const center = useMemo(() => (
+ {lat: 23.687, lng: -102.74}
+ ),[])
+
+ return (
+
+ {!isLoaded
+ ?
+
+ :
+
+ {marker && }
+
+ }
+
+ );
+}
\ No newline at end of file
diff --git a/web/src/components/multiple_images_dropzone/assets/css/styles.css b/web/src/components/multiple_images_dropzone/assets/css/styles.css
new file mode 100644
index 0000000000000000000000000000000000000000..8078660d67f1a9dc4b77b006e30178849cee4849
--- /dev/null
+++ b/web/src/components/multiple_images_dropzone/assets/css/styles.css
@@ -0,0 +1,69 @@
+.multiple_images_dropzone_root{
+ width: 100%;
+ height: 100px;
+ display: flex;
+ flex-direction: column;
+}
+
+.multiple_image_dropzone{
+ height: 20%;
+ width: 100%;
+ cursor: pointer;
+ border-color: #eeeeee;
+ border-style: dashed;
+ background-color: #fafafa;
+ outline: none;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.multiple_image_dropzone:hover {
+ border-color: rgb(106, 188, 226);
+ color: rgb(43, 38, 38);
+}
+
+.previews_list_cnt{
+ height: 80%;
+ width: 100%;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: start;
+ overflow-x: auto;
+}
+
+.previews_list_cnt .preview{
+ display: inline-flex;
+ border-radius: 2;
+ cursor: pointer;
+ border: 2px solid #eaeaea;
+ width: 60px;
+ height: 60px;
+ box-sizing: border-box;
+ margin-right: 2px;
+ position: relative;
+}
+
+.previews_list_cnt .preview:hover{
+ border: 2px solid #aba7a7;
+}
+
+.previews_list_cnt .preview .remove_button{
+ position: absolute;
+ right: 0px;
+ cursor: pointer;
+ color: red;
+}
+
+.previews_list_cnt .preview .preview_inner{
+ display: flex;
+ min-width: 0;
+ overflow: hidden;
+}
+
+.previews_list_cnt .preview .preview_inner img{
+ display: block;
+ width: auto;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/web/src/components/multiple_images_dropzone/multiple_images_dropzone.tsx b/web/src/components/multiple_images_dropzone/multiple_images_dropzone.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..72daeca9786f82c50dcc3da3c6dcb7e986cbfd48
--- /dev/null
+++ b/web/src/components/multiple_images_dropzone/multiple_images_dropzone.tsx
@@ -0,0 +1,54 @@
+import { UseFormSetValue } from "react-hook-form";
+import { PlaceFormValues } from "../../infraestructure/entities/place";
+import { useDropzoneMultiplesImages } from "../../hooks/useDropzoneMultiplesImages";
+import "./assets/css/styles.css";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faTimesCircle } from "@fortawesome/free-solid-svg-icons";
+
+interface props{
+ setValue: UseFormSetValue;
+}
+
+export const MultipleImagesDropzone = ({setValue}:props) => {
+ const {
+ getRootProps,
+ getInputProps,
+ imagesFiles,
+ removeImage,
+ } = useDropzoneMultiplesImages(setValue);
+
+ return (
+
+
+
+
Arrastra tu imagen o seleccionala dando click aquí.
+
+
+ {
+ imagesFiles &&
+ imagesFiles.map((image, index) => {
+ return (
+
{
+
+ }}
+ >
+
+
{URL.revokeObjectURL(image.preview)}}
+ />
+
+
removeImage(index)}
+ />
+
+ );
+ })
+ }
+
+
+ );
+}
+
diff --git a/web/src/components/sa_panel_admin/sa_panel_admin_register/assets/css/styles.css b/web/src/components/sa_panel_admin/sa_panel_admin_register/assets/css/styles.css
new file mode 100644
index 0000000000000000000000000000000000000000..5eca8d6e8e4c5acf203e14c8e566df8b5dc2b08e
--- /dev/null
+++ b/web/src/components/sa_panel_admin/sa_panel_admin_register/assets/css/styles.css
@@ -0,0 +1,92 @@
+*{
+ user-select: none;
+}
+
+.admin_register_wrap {
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ margin: auto;
+ width: 30vw;
+ height: 60vh;
+ background: green;
+ display: flex;
+ flex-direction: column;
+}
+
+.admin_register_header {
+ display: flex;
+ width: 100%;
+ align-items: center;
+ justify-content: center;
+ padding: 5px;
+}
+
+.admin_register_close_btn{
+ display: inline-block;
+ cursor: pointer;
+ position: absolute;
+ right: 5px;
+}
+
+.admin_register_content {
+ background: white;
+ width: 100%;
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+.admin_register_content form{
+ display: flex;
+ flex-direction: column;
+ flex-grow: 1;
+}
+
+.admin_input_cnt{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ padding: 5px 0;
+}
+
+.admin_input_cnt input{
+ width: 80%;
+ padding: 5px 20px;
+ border: 1px solid lightgray;
+ border-radius: 5px;
+}
+
+.error{
+ color: red;
+ font-size: 12px;
+ padding: 0;
+ margin: 0;
+}
+
+.admin_input_cnt .submit_btn{
+ width: 30%;
+}
+
+.password_input_visibility_cnt{
+ position: relative;
+ width: 80%;
+}
+
+.password_input_visibility_cnt input{
+ width: 100%;
+ padding-left: 20px;
+ padding-right: 30px;
+}
+
+.pass-visibility-button{
+ position: absolute;
+ top: 50%;
+ right: 5px;
+ transform: translateY(-50%);
+ transform: all 0.3s ease;
+ cursor: pointer;
+}
\ No newline at end of file
diff --git a/web/src/components/sa_panel_admin/sa_panel_admin_register/sa_panel_admin_register.tsx b/web/src/components/sa_panel_admin/sa_panel_admin_register/sa_panel_admin_register.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..89bb73a19924226641eb05af295f467507e849b9
--- /dev/null
+++ b/web/src/components/sa_panel_admin/sa_panel_admin_register/sa_panel_admin_register.tsx
@@ -0,0 +1,174 @@
+import { Dispatch, SetStateAction, useEffect, useState } from "react";
+import './assets/css/styles.css'
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faWindowClose, faEye, faEyeSlash } from "@fortawesome/free-solid-svg-icons";
+import { useAdminRegister } from "../../../hooks/useAdminRegister";
+import { usePasswoordVisibility } from "../../../hooks/usePasswordVisibility";
+import { State } from "../../../infraestructure/entities/state";
+import { useTown } from "../../../hooks/useTown";
+import { Town } from "../../../infraestructure/entities/town";
+import axios, { AxiosError } from "axios";
+import { ToastContainer, toast } from "react-toastify";
+
+interface props {
+ setWindowActive: Dispatch>,
+ setShowRegisterPanel: Dispatch>,
+ statesList : State[],
+}
+
+export const SuperadminPanelAdminRegister = ({setWindowActive, setShowRegisterPanel, statesList}:props) => {
+ const {
+ register,
+ errors,
+ handleSubmit,
+ onSubmit,
+ } = useAdminRegister();
+
+ const {
+ values,
+ handleClickShowPassword,
+ handleMouseDownPassword
+ } = usePasswoordVisibility();
+
+ const {townsList, getTownsByState, setTownsList} = useTown();
+
+ useEffect(() => {
+ const getTownsList = async () => {
+ try {
+ if (statesList) {
+ const townsListBackup: Town[] = [];
+ statesList.forEach((state) => {
+ getTownsByState(state.stateId, state.name);
+ if (townsList) {
+ townsListBackup.push(...townsList);
+ }
+ });
+ setTownsList(townsListBackup);
+ }
+ } catch (error: any) {
+ if (axios.isAxiosError(error)) {
+ error as AxiosError;
+ let message = "";
+ switch(error.code){
+ case(axios.AxiosError.ERR_BAD_REQUEST):
+ message = "Acceso no autorizado";
+ break;
+ case(axios.AxiosError.ERR_NETWORK):
+ message = "Conexión con el servidor fallida";
+ break;
+ }
+ toast.error(message, {
+ position: "bottom-right",
+ autoClose: 1500,
+ hideProgressBar: false,
+ closeOnClick: true,
+ pauseOnHover: false,
+ draggable: true,
+ progress: undefined,
+ theme: "colored"
+ });
+ }
+ }
+ }
+ getTownsList();
+ }, []);
+
+ return (
+
+
+ Registra el administrador
+ {setShowRegisterPanel(false); setWindowActive(false)}}/>
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/web/src/components/sa_panel_admin/sa_panel_admin_screen/assets/css/styles.css b/web/src/components/sa_panel_admin/sa_panel_admin_screen/assets/css/styles.css
new file mode 100644
index 0000000000000000000000000000000000000000..6d1ac315a125e4f50e63031a634e8c117104fff5
--- /dev/null
+++ b/web/src/components/sa_panel_admin/sa_panel_admin_screen/assets/css/styles.css
@@ -0,0 +1,28 @@
+.sa_panel_admin_content{
+ background: gray;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+.admin_panel_header{
+ display: flex;
+ width: 100%;
+ height: 7%;
+ align-items: center;
+ justify-content: center;
+ padding: 5px;
+}
+
+.admin_panel_header .admin_add_btn{
+ display: inline-block;
+ cursor: pointer;
+ position: absolute;
+ right: 5px;
+}
+
+.sa_panel_admin_body{
+ min-height: 93%;
+ max-height: 93%;
+}
\ 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
index b91f596188474ba0831d775a137fc84fbb862ac0..d12a591342b723a69ebb621139dca007b4f22619 100644
--- 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
@@ -1,8 +1,38 @@
+import { Dispatch, SetStateAction, useState } from 'react';
+import './assets/css/styles.css';
+import { SuperadminPanelAdminRegister } from '../sa_panel_admin_register/sa_panel_admin_register';
+import { State } from '../../../infraestructure/entities/state';
+
+interface props {
+ windowActive: boolean;
+ setWindowActive: Dispatch>;
+ statesList: State[];
+}
+
+export const SuperadminPanelAdminScreen = ({windowActive, setWindowActive, statesList}:props) => {
+ const [showRegisterPanel, setShowRegisterPanel] = useState(false);
-export const SuperadminPanelAdminScreen = () => {
return (
-
+
+ Administrar administradores
+
+
+
+ {showRegisterPanel
+ &&
+
+ }
+
);
}
\ No newline at end of file
diff --git a/web/src/components/sa_panel_town/sa_panel_town_list/sa_panel_town_list_content/assets/css/styles.css b/web/src/components/sa_panel_town/sa_panel_town_list/sa_panel_town_list_content/assets/css/styles.css
new file mode 100644
index 0000000000000000000000000000000000000000..5834a88ab4dd150b84d99e68b4832cef80eb2bfb
--- /dev/null
+++ b/web/src/components/sa_panel_town/sa_panel_town_list/sa_panel_town_list_content/assets/css/styles.css
@@ -0,0 +1,5 @@
+.town_list_content{
+ background: white;
+ width: 100%;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/web/src/components/sa_panel_town/sa_panel_town_list/sa_panel_town_list_content/sa_panel_town_list.tsx b/web/src/components/sa_panel_town/sa_panel_town_list/sa_panel_town_list_content/sa_panel_town_list.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a06aa5890c05f848dacc7087c12262e606b33f01
--- /dev/null
+++ b/web/src/components/sa_panel_town/sa_panel_town_list/sa_panel_town_list_content/sa_panel_town_list.tsx
@@ -0,0 +1,57 @@
+import { useEffect, useState } from "react";
+import { useTown } from "../../../../hooks/useTown";
+import { State } from "../../../../infraestructure/entities/state";
+import axios from "axios";
+import "./assets/css/styles.css";
+import { TownListTable } from "../sa_panel_town_list_element/sa_panel_town_table";
+import { Town } from "../../../../infraestructure/entities/town";
+import { LoadingScreen } from "../../../loading_screen/loading_screen";
+
+interface props {
+ statesList: State[];
+}
+
+export const SuperadminPanelTownList = ({statesList}: props) => {
+ const {townsList, getTownsByState, setTownsList} = useTown();
+ const [isLoadingList, setIsLoadingList] = useState(true);
+
+ useEffect(() => {
+ refreshList();
+ }, []);
+
+ const refreshList = () => {
+ setIsLoadingList(true);
+ const getTownsList = async () => {
+ try {
+ if (statesList) {
+ const townsListBackup: Town[] = [];
+ statesList.forEach((state) => {
+ getTownsByState(state.stateId, state.name);
+ if (townsList) {
+ townsListBackup.push(...townsList);
+ }
+ });
+ setTownsList(townsListBackup);
+ }
+ } catch (error: any) {
+ if (axios.isAxiosError(error)) {
+ //console.log(error)
+ }
+ }
+ }
+ getTownsList();
+ };
+
+ return (
+
+ {
+ isLoadingList &&
+
+ }
+ {
+ townsList && townsList.length>0 &&
+
+ }
+
+ );
+}
\ No newline at end of file
diff --git a/web/src/components/sa_panel_town/sa_panel_town_list/sa_panel_town_list_element/assets/css/styles.css b/web/src/components/sa_panel_town/sa_panel_town_list/sa_panel_town_list_element/assets/css/styles.css
new file mode 100644
index 0000000000000000000000000000000000000000..7e61c5a7d08f09bd611de1ee4530948d2056ab20
--- /dev/null
+++ b/web/src/components/sa_panel_town/sa_panel_town_list/sa_panel_town_list_element/assets/css/styles.css
@@ -0,0 +1,13 @@
+.town_list_table {
+ height: 100%;
+}
+
+.bhFeAR{
+ display: flex !important;
+ height: 100%;
+}
+
+.rdt_TableBody{
+ max-height: 100%;
+ overflow-y: auto;
+}
\ No newline at end of file
diff --git a/web/src/components/sa_panel_town/sa_panel_town_list/sa_panel_town_list_element/sa_panel_town_table.tsx b/web/src/components/sa_panel_town/sa_panel_town_list/sa_panel_town_list_element/sa_panel_town_table.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..fceb4b045a415d299cc87741e31907dfd1d47270
--- /dev/null
+++ b/web/src/components/sa_panel_town/sa_panel_town_list/sa_panel_town_list_element/sa_panel_town_table.tsx
@@ -0,0 +1,35 @@
+import { Dispatch, SetStateAction, useEffect } from "react";
+import { Town } from "../../../../infraestructure/entities/town";
+import "./assets/css/styles.css";
+import DataTable, { TableColumn } from "react-data-table-component";
+
+interface props {
+ towns: Town[];
+ setIsLoading: Dispatch>;
+}
+
+export const TownListTable= ({towns, setIsLoading}: props) => {
+ const columns : TableColumn[] = [
+ {
+ name: "Identificador",
+ selector: row => row.idTown
+ },
+ {
+ name: "Nombre",
+ selector: row => row.name,
+ sortable: true
+ },
+ {
+ name: "Estado",
+ selector: row => row.state
+ }
+ ];
+
+ useEffect(() => {
+ setIsLoading(false);
+ }, []);
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/web/src/components/sa_panel_town/sa_panel_town_register/css/styles.css b/web/src/components/sa_panel_town/sa_panel_town_register/css/styles.css
index 2445be2fb3d51e905731dcbe9f677e562c51e221..67c03469df77f0cd6de44bebba0308e1d546b3c1 100644
--- a/web/src/components/sa_panel_town/sa_panel_town_register/css/styles.css
+++ b/web/src/components/sa_panel_town/sa_panel_town_register/css/styles.css
@@ -58,7 +58,7 @@
width: 55%;
display: flex;
flex-direction: column;
- padding: 20px;
+ padding: 10px;
}
.image_container {
@@ -99,7 +99,7 @@
.state_select{
display: flex;
flex-direction: column;
- width: 40%;
+ width: 50%;
justify-content: center;
align-items: center;
}
@@ -123,7 +123,7 @@
}
.town_name_input{
- width: 40%;
+ width: 50%;
display: flex;
flex-direction: column;
justify-content: center;
@@ -152,4 +152,20 @@
flex-direction: row;
justify-content: center;
align-items: center;
+}
+
+.language_change_cnt label{
+ font-size: 10px;
+}
+
+.towm_desc_input_cnt{
+ display: flex;
+ flex-direction: column;
+}
+
+.error{
+ color: red;
+ font-size: 12px;
+ padding: 0;
+ margin: 0;
}
\ No newline at end of file
diff --git a/web/src/components/sa_panel_town/sa_panel_town_register/sa_panel_town_register.tsx b/web/src/components/sa_panel_town/sa_panel_town_register/sa_panel_town_register.tsx
index 9f796dd5564228db3e8222e7b6ebcaa0fa12968a..e1a09af73d95d975787cececba5634ec455ea8e9 100644
--- a/web/src/components/sa_panel_town/sa_panel_town_register/sa_panel_town_register.tsx
+++ b/web/src/components/sa_panel_town/sa_panel_town_register/sa_panel_town_register.tsx
@@ -3,22 +3,24 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import './css/styles.css'
import { ImageDropzone } from "../../image_dropzone/image_dropzone";
import { Dispatch, SetStateAction, useState } from "react";
-import { useTownRegister } from "../../../hooks/useTownRegister";
+import { useTown } from "../../../hooks/useTown";
+import { State } from "../../../infraestructure/entities/state";
interface props {
setWindowActive: Dispatch>,
setShowRegisterPanel: Dispatch>
+ statesList: State[] | null;
+ forceRenderList: () => void;
}
-export const SuperadminPanelTownRegister = ({setWindowActive, setShowRegisterPanel}:props) => {
+export const SuperadminPanelTownRegister = ({setWindowActive, setShowRegisterPanel, statesList, forceRenderList}:props) => {
const {
- statesList,
register,
- setValue,
+ setValue,
+ errors,
handleSubmit,
- errors,
- onSubmit
- } = useTownRegister();
+ onSubmit,
+ } = useTown(forceRenderList);
const [isEnglish, setIsEnglish] = useState(false);
const [spanishDescription, setSpanishDescription] = useState("");
const [englishDescription, setEnglishDescription] = useState("");
@@ -33,36 +35,21 @@ export const SuperadminPanelTownRegister = ({setWindowActive, setShowRegisterPan
);
diff --git a/web/src/constants/api_keys.ts b/web/src/constants/api_keys.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cd8c9ac6f988fdd92d13f0ceb81ec62243011d29
--- /dev/null
+++ b/web/src/constants/api_keys.ts
@@ -0,0 +1 @@
+export const REACT_APP_GOOGLE_API_KEY: string = "AIzaSyBpAU7mFuX3oAyRisHiMI89fRcLuRA_i3o";
\ No newline at end of file
diff --git a/web/src/constants/images_nuber.ts b/web/src/constants/images_nuber.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d9b2beb79512f6468873b47f1ec3ec27ee9af327
--- /dev/null
+++ b/web/src/constants/images_nuber.ts
@@ -0,0 +1 @@
+export const MIN_NUMBER_PLACE_IMAGES = 1;
\ No newline at end of file
diff --git a/web/src/constants/languages.ts b/web/src/constants/languages.ts
new file mode 100644
index 0000000000000000000000000000000000000000..770db8d7cd20d9db7aaa514acfd401930948b9db
--- /dev/null
+++ b/web/src/constants/languages.ts
@@ -0,0 +1,4 @@
+export const languaguesList: string[] = [
+ 'Español',
+ 'Inglés',
+]
\ No newline at end of file
diff --git a/web/src/constants/roles.ts b/web/src/constants/roles.ts
index 02cddf704502cf502565b1bb76d13ecee2d0b2b2..dd45c3294fc22de5ccefa0a6913961853b9ae1b6 100644
--- a/web/src/constants/roles.ts
+++ b/web/src/constants/roles.ts
@@ -3,5 +3,5 @@ export enum UserRole {
SUPERADMIN = "superadmin",
}
-export const SUPERADMIN_ROLES = [UserRole.SUPERADMIN];
-export const ADMIN_ROLES = [UserRole.ADMIN, UserRole.SUPERADMIN];
\ No newline at end of file
+export const SUPERADMIN_ROLE = UserRole.SUPERADMIN;
+export const ADMIN_ROLE = UserRole.ADMIN;
\ No newline at end of file
diff --git a/web/src/constants/selected_panel.ts b/web/src/constants/selected_panel.ts
new file mode 100644
index 0000000000000000000000000000000000000000..90b3d4e69f5df6f80571e67a5c81484d41a8e80d
--- /dev/null
+++ b/web/src/constants/selected_panel.ts
@@ -0,0 +1,4 @@
+export enum AdminSelectedPanel {
+ PLACES = "places",
+ ACTIVITIES = "activities"
+}
\ No newline at end of file
diff --git a/web/src/context/auth_context.tsx b/web/src/context/auth_context.tsx
index 2648700def5b1f352a261097a596cfdd34c1e7b5..0a7771410adc573089825719b2e4c4cd3add4e27 100644
--- a/web/src/context/auth_context.tsx
+++ b/web/src/context/auth_context.tsx
@@ -25,7 +25,7 @@ export const AuthContextProvider = ({children} : AuthContextProviderProps) => {
setUser(user);
localStorage.setItem("token",token);
localStorage.setItem("user",JSON.stringify(user));
- axios.defaults.headers.common["Authorization"] = `${token}`;
+ axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
}
const deleteSession = async () => {
@@ -48,7 +48,7 @@ export const AuthContextProvider = ({children} : AuthContextProviderProps) => {
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);
+ await saveSession({email: sessionUser.email as string, name: sessionUser.name as string, role: sessionUser.role as UserRole}, token);
}
}
diff --git a/web/src/data/datasources/prod/admin_datasource.ts b/web/src/data/datasources/prod/admin_datasource.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fe19d185d1010fa3020f3b672800889c26e18fc5
--- /dev/null
+++ b/web/src/data/datasources/prod/admin_datasource.ts
@@ -0,0 +1,8 @@
+import { AdminDatasourceInf } from "../../../infraestructure/datasources/admin_datasource";
+import { AdminFormValues } from "../../../infraestructure/entities/admin_form_values";
+
+export class AdminDatasourceProd implements AdminDatasourceInf{
+ async registerAdmin(form: AdminFormValues): Promise {
+
+ }
+}
\ No newline at end of file
diff --git a/web/src/data/datasources/prod/place_datasource.ts b/web/src/data/datasources/prod/place_datasource.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ff214c3bec11e8955087d6d36e291c6c5d886e6d
--- /dev/null
+++ b/web/src/data/datasources/prod/place_datasource.ts
@@ -0,0 +1,31 @@
+import axios from "axios";
+import { APIUrl } from "../../../constants/api_url";
+import { PlaceDatasourceInf } from "../../../infraestructure/datasources/place_datasource";
+import { AvailableDays, PlaceFormValues } from "../../../infraestructure/entities/place";
+
+export class PlaceDatasourceProd implements PlaceDatasourceInf{
+ async registerPlace(form: PlaceFormValues): Promise {
+ const formToSend = new FormData();
+ formToSend.append('available', form.available);
+ formToSend.append('idTown', String(form.idTown));
+ formToSend.append('name', form.name);
+ formToSend.append('descriptionES', form.descriptions[0]);
+ formToSend.append('descriptionEN', form.descriptions[1]);
+ formToSend.append('image', form.imagesList[0]);
+ formToSend.append('latitude', String(form.latitude));
+ formToSend.append('longitude', String(form.longitude));
+ formToSend.append('openAt', String(form.openAt));
+ formToSend.append('closeAt', String(form.closeAt));
+
+ if(form.available === AvailableDays.CUSTOM){
+ formToSend.append('startDate', String(form.startDate));
+ formToSend.append('endDate', String(form.endDate));
+ }
+
+ const headers = {
+ 'Content-Type': 'multipart/form-data'
+ };
+
+ await axios.post(APIUrl + '/place', formToSend,{headers});
+ }
+}
\ No newline at end of file
diff --git a/web/src/data/datasources/prod/town_datasource.ts b/web/src/data/datasources/prod/town_datasource.ts
index f398dcff56eaff5f7e835f4c5f69db8c3150d319..b74169f57e451b714fc2d3651232a56c41ef39eb 100644
--- a/web/src/data/datasources/prod/town_datasource.ts
+++ b/web/src/data/datasources/prod/town_datasource.ts
@@ -3,8 +3,8 @@ import { APIUrl } from "../../../constants/api_url";
import { TownDatasourceInf } from "../../../infraestructure/datasources/town_datasource";
import { StateModel } from "../../models/prod/StateModel";
import { State } from "../../../infraestructure/entities/state";
-import { TownFormValues } from "../../../infraestructure/entities/town_form_values";
-import UnauthorizedError from "../../../errors/UnautherizedError";
+import { Town, TownFormValues } from "../../../infraestructure/entities/town";
+import { TownModel } from "../../models/prod/TownModel";
export class TownDatasourceProd implements TownDatasourceInf{
async getStates(): Promise {
@@ -22,7 +22,7 @@ export class TownDatasourceProd implements TownDatasourceInf{
}
async registerTown(form: TownFormValues): Promise {
- const formToSend = new FormData;
+ const formToSend = new FormData();
formToSend.append('name',form.name);
formToSend.append('descriptionES',form.descriptionES);
formToSend.append('descriptionEN',form.descriptionEN);
@@ -33,11 +33,25 @@ export class TownDatasourceProd implements TownDatasourceInf{
'Content-Type': 'multipart/form-data'
};
- const {status, data} = await axios.post(APIUrl + '/town', formToSend,{headers});
- if (status === 401) {
- throw new UnauthorizedError({code: 401, message: 'Unauthorized'});
- }else {
- console.log(data);
- }
+ await axios.post(APIUrl + '/town', formToSend,{headers});
+ }
+
+ async getTownsByState(idState: number, stateName:string): Promise {
+ const {data} = await axios.get(APIUrl+`/state/${idState}/town`, {
+ params: {
+ lang: 'ES'
+ }
+ });
+ const towns = data.map((value) => {
+ const town: Town = {
+ idTown : value.townId,
+ name: value.name,
+ idState: value.stateId,
+ state: stateName
+ }
+ return town;
+ })
+
+ return towns;
}
}
\ No newline at end of file
diff --git a/web/src/data/models/prod/TownModel.ts b/web/src/data/models/prod/TownModel.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9505c9dcc23725bf4ed78332a2c33acfaab9935e
--- /dev/null
+++ b/web/src/data/models/prod/TownModel.ts
@@ -0,0 +1,7 @@
+export interface TownModel {
+ townId: number;
+ name : string;
+ imageName : string;
+ description : string;
+ stateId : number;
+}
\ No newline at end of file
diff --git a/web/src/data/repositories/prod/admin_repository.ts b/web/src/data/repositories/prod/admin_repository.ts
new file mode 100644
index 0000000000000000000000000000000000000000..81724b76e3f210473b9d0db1ab6c50006561ea70
--- /dev/null
+++ b/web/src/data/repositories/prod/admin_repository.ts
@@ -0,0 +1,13 @@
+import { AdminDatasourceInf } from "../../../infraestructure/datasources/admin_datasource";
+import { AdminFormValues } from "../../../infraestructure/entities/admin_form_values";
+import { AdminRepositoryInf } from "../../../infraestructure/repositories/admin_repository";
+
+export class AdminRepositoryProd implements AdminRepositoryInf{
+ constructor(
+ private datasource: AdminDatasourceInf
+ ){}
+
+ async registerAdmin(form: AdminFormValues): Promise {
+ return this.datasource.registerAdmin(form);
+ }
+}
\ No newline at end of file
diff --git a/web/src/data/repositories/prod/place_repository.ts b/web/src/data/repositories/prod/place_repository.ts
new file mode 100644
index 0000000000000000000000000000000000000000..25da377752dc1d833d33bad1de421e6557b8fa6e
--- /dev/null
+++ b/web/src/data/repositories/prod/place_repository.ts
@@ -0,0 +1,12 @@
+import { PlaceDatasourceInf } from "../../../infraestructure/datasources/place_datasource";
+import { PlaceFormValues } from "../../../infraestructure/entities/place";
+import { PlaceRepositoryInf } from "../../../infraestructure/repositories/place_repository";
+
+export class PlaceRepositoryProd implements PlaceRepositoryInf{
+ constructor(
+ private datasouce: PlaceDatasourceInf
+ ){}
+ async registerPlace(form: PlaceFormValues): Promise {
+ return this.datasouce.registerPlace(form);
+ }
+}
\ No newline at end of file
diff --git a/web/src/data/repositories/prod/town_repository.ts b/web/src/data/repositories/prod/town_repository.ts
index 2ca49d14c33a5fd22a7f2932246a8044d985c091..31c8edd0ac5ded9cb38da9395cc99722c9819ffe 100644
--- a/web/src/data/repositories/prod/town_repository.ts
+++ b/web/src/data/repositories/prod/town_repository.ts
@@ -1,6 +1,6 @@
import { TownDatasourceInf } from "../../../infraestructure/datasources/town_datasource";
import { State } from "../../../infraestructure/entities/state";
-import { TownFormValues } from "../../../infraestructure/entities/town_form_values";
+import { Town, TownFormValues } from "../../../infraestructure/entities/town";
import { TownRepositoryInf } from "../../../infraestructure/repositories/town_repository";
export class TownRepositoryProd implements TownRepositoryInf{
@@ -13,4 +13,7 @@ export class TownRepositoryProd implements TownRepositoryInf{
async registerTown(form: TownFormValues): Promise {
return this.datasource.registerTown(form);
}
+ async getTownsByState(idState: number, stateName:string) : Promise {
+ return this.datasource.getTownsByState(idState, stateName);
+ }
}
\ No newline at end of file
diff --git a/web/src/hooks/useAdminNavbar.tsx b/web/src/hooks/useAdminNavbar.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c4792abb4dfbbe31e465ae64a7f09dac01ce90c1
--- /dev/null
+++ b/web/src/hooks/useAdminNavbar.tsx
@@ -0,0 +1,21 @@
+import { useState } from "react";
+import { useAuth } from "../context/auth_context";
+import { Convert, UserEntity } from "../infraestructure/entities/user";
+
+export const useAdminNavbar = () => {
+ const {user, logout} = useAuth();
+ const [toggle, setToggle] = useState(false);
+
+ const userDataJson: string | null = localStorage.getItem("user");
+ let userData: UserEntity | undefined;
+
+ if(userDataJson){
+ userData = Convert.toUser(userDataJson);
+ }
+
+ const handleLogout = () => {
+ logout();
+ };
+
+ return {user, handleLogout, setToggle, toggle, userData};
+}
\ No newline at end of file
diff --git a/web/src/hooks/useAdminRegister.tsx b/web/src/hooks/useAdminRegister.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..02b38e9bd4f93709d461fa697fcb4cb85c3ce724
--- /dev/null
+++ b/web/src/hooks/useAdminRegister.tsx
@@ -0,0 +1,94 @@
+import { FieldErrors, Resolver, SubmitHandler, useForm } from "react-hook-form";
+import { AdminDatasourceProd } from "../data/datasources/prod/admin_datasource";
+import { AdminRepositoryProd } from "../data/repositories/prod/admin_repository";
+import { AdminFormValues } from "../infraestructure/entities/admin_form_values";
+import { toast } from "react-toastify";
+
+const adminDatasource = new AdminDatasourceProd();
+const adminRepository = new AdminRepositoryProd(adminDatasource);
+
+const resolver: Resolver = async (data) => {
+ const errors: FieldErrors = {};
+
+ if (!data.name) {
+ errors.name = {
+ type: "required",
+ message: "El nombre del administrador es requerido"
+ };
+ }
+
+ if (!data.email) {
+ errors.email = {
+ type: "required",
+ message: "El correo electronico es requerido"
+ };
+ }else{
+ const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
+ if (!emailPattern.test(data.email)) {
+ errors.email = {
+ type: "validate",
+ message: "El correo electronico no es válido"
+ };
+ }
+ }
+
+ if (!data.password) {
+ errors.password = {
+ type: "required",
+ message: "La contraseña es requerida"
+ };
+ }else if(data.confirmPassword!=data.password){
+ errors.confirmPassword = {
+ type: "validate",
+ message: "Las contraseñas deben coincidir"
+ };
+ }
+
+ if (!data.confirmPassword) {
+ errors.confirmPassword = {
+ type: "required",
+ message: "La contraseña es requerida"
+ };
+ }
+
+ if (!data.townAdmin) {
+ errors.townAdmin = {
+ type: "required",
+ message: "Elija el pueblo al que va a representar"
+ };
+ }
+
+ return {
+ values: Object.keys(errors).length > 0 ? {} : data,
+ errors: errors,
+ };
+};
+
+export const useAdminRegister = () => {
+ const {
+ register,
+ handleSubmit,
+ formState: {errors},
+ setValue
+ } = useForm({resolver});
+
+ const onSubmit: SubmitHandler = (data: AdminFormValues) => {
+ const fetch = async () => {
+ try{
+ //await adminRepository.registerAdmin(data);
+ console.log(data);
+ }catch(error: any){
+
+ }
+ }
+ toast.promise(
+ fetch(),{
+ pending: "Subiendo datos...",
+ success: "Los datos se han subido correctamente",
+ error: "Sucedio un error"
+ }
+ )
+ }
+
+ return {register, handleSubmit, errors, onSubmit};
+}
\ No newline at end of file
diff --git a/web/src/hooks/useDropzoneMultiplesImages.tsx b/web/src/hooks/useDropzoneMultiplesImages.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ac96f91a37a65bc22a7f684641825da5e9f5122c
--- /dev/null
+++ b/web/src/hooks/useDropzoneMultiplesImages.tsx
@@ -0,0 +1,70 @@
+import { useState } from "react";
+import { useDropzone } from "react-dropzone";
+import { toast } from "react-toastify";
+import { Image } from "../infraestructure/entities/image";
+import { UseFormSetValue } from "react-hook-form";
+import { PlaceFormValues } from "../infraestructure/entities/place";
+
+export const useDropzoneMultiplesImages = (setValue: UseFormSetValue) => {
+ const [imagesFiles, setImagesFiles] = useState([]);
+ const [files, setFiles] = useState([]);
+ const MAX_SIZE = 10485760;
+ const {getRootProps, getInputProps} = useDropzone(
+ {
+ maxSize: MAX_SIZE,
+ accept: {
+ 'image/*': []
+ },
+ onDrop(acceptedFiles, fileRejections) {
+ fileRejections.map(({file, errors}) => (
+ toast.error("Error: " + errors[0].message +" : "+ file.name, {
+ position: "bottom-right",
+ autoClose: 1500,
+ hideProgressBar: false,
+ closeOnClick: true,
+ pauseOnHover: false,
+ draggable: true,
+ progress: undefined,
+ theme: "colored"
+ })
+ ));
+
+ const backupImages = [...imagesFiles];
+ const backupFiles = [...files];
+ acceptedFiles.forEach((file, index)=>{
+ if(imagesFiles.length<=10 && index<10){
+ const preview = URL.createObjectURL(file);
+ backupImages.push({file, preview});
+ backupFiles.push(file);
+ }else{
+ toast.error("Error: Ha superado los 10 archivos", {
+ position: "bottom-right",
+ autoClose: 1500,
+ hideProgressBar: false,
+ closeOnClick: true,
+ pauseOnHover: false,
+ draggable: true,
+ progress: undefined,
+ theme: "colored"
+ });
+ }
+ });
+ setImagesFiles(backupImages);
+ setFiles(backupFiles);
+ setValue("imagesList", backupFiles, {shouldValidate:true});
+ }
+ }
+ );
+
+ const removeImage = (index: number) => {
+ const backupImages = [...imagesFiles];
+ const backupFiles = [...files];
+ backupImages.splice(index, 1);
+ backupFiles.splice(index, 1);
+ setImagesFiles(backupImages);
+ setFiles(backupFiles);
+ setValue("imagesList", backupFiles, {shouldValidate:true});
+ }
+
+ return {getInputProps, getRootProps, imagesFiles, removeImage};
+}
\ No newline at end of file
diff --git a/web/src/hooks/useGetStatesList.tsx b/web/src/hooks/useGetStatesList.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b862ff743dd02c94845434a592aa4c937109c6e4
--- /dev/null
+++ b/web/src/hooks/useGetStatesList.tsx
@@ -0,0 +1,23 @@
+import { useState } from "react";
+import { State } from "../infraestructure/entities/state";
+import axios from "axios";
+import { TownDatasourceProd } from "../data/datasources/prod/town_datasource";
+import { TownRepositoryProd } from "../data/repositories/prod/town_repository";
+const townDatasource = new TownDatasourceProd();
+const townRepository = new TownRepositoryProd(townDatasource);
+
+export const useGetStatesList = () => {
+ const [statesList, setStatesList] = useState([]);
+
+ const getStates = async () => {
+ try{
+ const states = await townRepository.getStates();
+ setStatesList(states);
+ }catch(error: any){
+ if(axios.isAxiosError(error)){
+ //console.log(error)
+ }
+ }
+ }
+ return {getStates, statesList};
+}
\ No newline at end of file
diff --git a/web/src/hooks/useLogin.tsx b/web/src/hooks/useLogin.tsx
index 6a8ec16917269db00aac523e77ea0761715b1cba..44cfe6ea596aa92b5641c9d7f94477f0b5f9cf3e 100644
--- a/web/src/hooks/useLogin.tsx
+++ b/web/src/hooks/useLogin.tsx
@@ -1,14 +1,46 @@
-import { SubmitHandler, useForm } from "react-hook-form"
+import { FieldErrors, Resolver, 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 { Dispatch, SetStateAction, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { useAuth } from "../context/auth_context";
+import axios, { AxiosError } from "axios";
const loginDatasource = new LoginDatasourceProd();
const loginRepository = new LoginRepositoryProd(loginDatasource);
-export const useLogin = () => {
+const resolver: Resolver = async (data) => {
+ const errors: FieldErrors = {};
+
+ if (!data.email) {
+ errors.email = {
+ type: "required",
+ message: "El correo electronico es requerido"
+ };
+ }else{
+ const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
+ if (!emailPattern.test(data.email)) {
+ errors.email = {
+ type: "validate",
+ message: "El correo electronico no es válido"
+ };
+ }
+ }
+
+ if (!data.password) {
+ errors.password = {
+ type: "required",
+ message: "La contraseña es requerida"
+ };
+ }
+
+ return {
+ values: Object.keys(errors).length > 0 ? {} : data,
+ errors: errors,
+ };
+};
+
+export const useLogin = (setIsLoading : Dispatch>) => {
const {login, user} = useAuth();
const navigate = useNavigate();
const {
@@ -16,17 +48,30 @@ export const useLogin = () => {
handleSubmit,
setError,
formState: {errors},
- } = useForm();
+ clearErrors,
+ } = useForm({resolver});
const onSubmit: SubmitHandler = (data: LoginFormValues) => {
const authenticate = async () => {
+ setIsLoading(true);
try{
const {user, token } = await loginRepository.getToken(data);
await login(user, token);
navigate("/");
}catch(error: any){
-
+ if(axios.isAxiosError(error)){
+ error as AxiosError;
+ switch(error.code){
+ case(axios.AxiosError.ERR_BAD_REQUEST):
+ setError("root.serverError", {type: "401", message: "Correo electrónico o contraseña incorrectos"});
+ break;
+ case(axios.AxiosError.ERR_NETWORK):
+ setError("root.serverError", {type: "500", message: "Conexión con el servidor fallida"});
+ break;
+ }
+ }
}
+ setIsLoading(false);
}
authenticate();
}
@@ -37,5 +82,5 @@ export const useLogin = () => {
}
}, []);
- return { register, handleSubmit, onSubmit, errors };
+ return { register, handleSubmit, onSubmit, errors , clearErrors};
}
\ No newline at end of file
diff --git a/web/src/hooks/usePlaceRegister.tsx b/web/src/hooks/usePlaceRegister.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6fb19965fc4eef0f8a4d5a87ba47654a1e328a15
--- /dev/null
+++ b/web/src/hooks/usePlaceRegister.tsx
@@ -0,0 +1,241 @@
+import { FieldErrors, Resolver, SubmitHandler, useForm } from "react-hook-form"
+import { PlaceFormValues, AvailableDays } from "../infraestructure/entities/place";
+import { toast } from "react-toastify";
+import { useEffect, useState } from "react";
+import axios, { AxiosError } from "axios";
+import { languaguesList } from "../constants/languages";
+import { useGetStatesList } from "./useGetStatesList";
+import { useTown } from "./useTown";
+import { MIN_NUMBER_PLACE_IMAGES } from "../constants/images_nuber";
+import { PlaceDatasourceProd } from "../data/datasources/prod/place_datasource";
+import { PlaceRepositoryProd } from "../data/repositories/prod/place_repository";
+
+const placeDatasouce = new PlaceDatasourceProd();
+const placeRepository = new PlaceRepositoryProd(placeDatasouce);
+
+const resolver: Resolver = async (data) => {
+ const errors: FieldErrors = {};
+
+ if(!data.idTown){
+ errors.idTown = {
+ type : "required",
+ message : "Debe seleccionar el pueblo mágico al que pertenece el lugar"
+ }
+ }
+
+ if(!data.openAt){
+ errors.openAt = {
+ type: "required",
+ message: "La hora de apertura es requerida"
+ };
+ }
+
+ if(!data.closeAt){
+ errors.closeAt = {
+ type: "required",
+ message: "La hora de cierre es requerida"
+ };
+ }
+
+ if(data.openAt>data.closeAt){
+ errors.closeAt = {
+ type: "required",
+ message: "La hora de apertura debe ser antes que la hora de cierre"
+ };
+ }
+
+ if(data.openAt===data.closeAt){
+ errors.closeAt = {
+ type: "required",
+ message: "Debe de haber al menos una hora de diferencia"
+ };
+ }
+
+ if(!data.name){
+ errors.name = {
+ type: "required",
+ message: "El nombre del lugar es requerido"
+ };
+ }
+
+ for(var index = languaguesList.length-1; index>=0; index--){
+ if(!data.descriptions || !data.descriptions[index]){
+ errors.descriptions = {
+ type: "required",
+ message: "La descripción del lugar en "+languaguesList[index]+" es requerida"
+ };
+ }
+ }
+
+ if(!data.available){
+ errors.available = {
+ type: "required",
+ message: "Se requieren los días que está disponible el lugar"
+ };
+ }else{
+ if(data.available === AvailableDays.CUSTOM){
+ if(!data.startDate){
+ errors.startDate = {
+ type: "required",
+ message: "Se requiere la fecha de inicio"
+ };
+ }
+
+ if(!data.endDate){
+ errors.endDate = {
+ type: "required",
+ message: "Se requiere la fecha de cierre"
+ };
+ }
+
+ if(data.startDate && data.endDate){
+ const startDate = new Date(data.startDate);
+ const endDate = new Date(data.endDate);
+ if(startDate.getTime() > endDate.getTime()){
+ errors.endDate = {
+ type: "required",
+ message: "La fecha de inicio no puede ser después de la fecha de cierre"
+ };
+ }
+ }
+ }
+ }
+
+ if(!data.latitude || !data.longitude){
+ errors.latitude = {
+ type: "required",
+ message: "Debe de seleccionar la ubicación del lugar"
+ }
+ }
+
+ if(!data.imagesList || data.imagesList.length 0 ? {} : data,
+ errors: errors
+ };
+};
+
+export const usePlaceRegister = () => {
+ const {
+ register,
+ handleSubmit,
+ setValue,
+ formState: {errors},
+ clearErrors,
+ resetField,
+ } = useForm({resolver});
+ const [errorMessage, setErrorMessage] = useState("");
+ const [languageDescriptionIndexSelected, setLanguageDescriptionIndexSelected] = useState(0);
+ const [descriptions, setDescriptions] = useState(new Array(languaguesList.length).fill(""));
+ const [availableDays, setAvailableDays] = useState(AvailableDays.WEEKEND);
+ const [isLoading, setIsLoading] = useState(false);
+ const {townsList, getTownsByState} = useTown();
+ const {getStates, statesList} = useGetStatesList();
+
+ useEffect(() => {
+ setIsLoading(true);
+ const getStatesList = async () => {
+ try{
+ getStates();
+ }catch(error: any){
+ if(axios.isAxiosError(error)){
+ error as AxiosError;
+ let message = "";
+ switch(error.code){
+ case(axios.AxiosError.ERR_BAD_REQUEST):
+ message = "Acceso no autorizado";
+ break;
+ case(axios.AxiosError.ERR_NETWORK):
+ message = "Conexión con el servidor fallida";
+ break;
+ }
+ toast.error(message, {
+ position: "bottom-right",
+ autoClose: 1500,
+ hideProgressBar: false,
+ closeOnClick: true,
+ pauseOnHover: false,
+ draggable: true,
+ progress: undefined,
+ theme: "colored"
+ });
+ }
+ }
+ }
+ getStatesList();
+ setValue('available',availableDays,{shouldValidate: true});
+ setIsLoading(false);
+ },[]);
+
+ const updateTownsList = (idState: number, stateName: string) => {
+ getTownsByState(idState, stateName);
+ }
+
+ const onSubmit : SubmitHandler = (data: PlaceFormValues) => {
+ const fetch = async () => {
+ try{
+ await placeRepository.registerPlace(data);
+ }catch(error: any){
+ if(axios.isAxiosError(error)){
+ error as AxiosError;
+ switch(error.code){
+ case(axios.AxiosError.ERR_BAD_REQUEST):
+ setErrorMessage("Acceso no autorizado");
+ break;
+ case(axios.AxiosError.ERR_NETWORK):
+ setErrorMessage("Conexión con el servidor fallida");
+ break;
+ }
+ }
+ throw new Error();
+ }
+ }
+ toast.promise(
+ fetch(),{
+ pending: "Subiendo datos...",
+ success: "Los datos se han subido correctamente",
+ error: errorMessage
+ }
+ )
+ }
+
+ const updateTimeForm = (time: string, isOpeningHour: boolean) => {
+ const timeSplitted = time.split(":");
+ let hours = Number(timeSplitted[0]);
+ const minutes = Number(timeSplitted[1]);
+ hours += (minutes/60);
+ hours = Math.round(hours);
+ if(isOpeningHour){
+ setValue("openAt", hours, {shouldValidate: true});
+ }else{
+ setValue("closeAt", hours, {shouldValidate: true});
+ }
+ }
+
+ return {
+ register,
+ handleSubmit,
+ errors,
+ onSubmit,
+ setValue,
+ languageDescriptionIndexSelected,
+ setLanguageDescriptionIndexSelected,
+ descriptions,
+ setDescriptions,
+ clearErrors,
+ isLoading,
+ statesList,
+ townsList,
+ updateTownsList,
+ updateTimeForm,
+ availableDays,
+ setAvailableDays,
+ resetField
+ };
+}
diff --git a/web/src/hooks/useTown.tsx b/web/src/hooks/useTown.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c260d6b166415bb4534588bc07f978a8b00edb66
--- /dev/null
+++ b/web/src/hooks/useTown.tsx
@@ -0,0 +1,107 @@
+import { useState } from "react";
+import { TownDatasourceProd } from "../data/datasources/prod/town_datasource"
+import { TownRepositoryProd } from "../data/repositories/prod/town_repository";
+import { FieldErrors, Resolver, SubmitHandler, useForm } from "react-hook-form";
+import { Town, TownFormValues } from "../infraestructure/entities/town";
+import { toast } from "react-toastify";
+import axios, { AxiosError } from "axios";
+
+const townDatasource = new TownDatasourceProd();
+const townRepository = new TownRepositoryProd(townDatasource);
+
+const resolver: Resolver = async (data) => {
+ const errors: FieldErrors = {};
+
+ if (!data.name) {
+ errors.name = {
+ type: "required",
+ message: "El nombre del pueblo es requerido"
+ };
+ }
+
+ if (!data.state) {
+ errors.state = {
+ type: "required",
+ message: "Debe seleccionar el estado al que pertenece el pueblo"
+ };
+ }
+
+ if (!data.descriptionES) {
+ errors.descriptionES = {
+ type: "required",
+ message: "La descripción del pueblo en español es requerida"
+ };
+ }
+
+ if (!data.descriptionEN) {
+ errors.descriptionEN = {
+ type: "required",
+ message: "La descripción del pueblo en inglés es requerida"
+ };
+ }
+
+ if (!data.imageURL) {
+ errors.imageURL = {
+ type: "required",
+ message: "Debe seleccionar la imagen representativa del pueblo"
+ };
+ }
+
+ return {
+ values: Object.keys(errors).length > 0 ? {} : data,
+ errors: errors,
+ };
+};
+
+export const useTown = (forceRenderList?: () => void) => {
+ const {
+ register,
+ handleSubmit,
+ formState: {errors},
+ setValue
+ } = useForm({resolver});
+ const [townsList, setTownsList] = useState(null);
+ const [errorMessage, setErrorMessage] = useState("");
+
+ const onSubmit: SubmitHandler = (data: TownFormValues) => {
+ const fetch = async () => {
+ try{
+ await townRepository.registerTown(data);
+ if(forceRenderList){
+ forceRenderList();
+ }
+ }catch(error: any){
+ if(axios.isAxiosError(error)){
+ error as AxiosError;
+ switch(error.code){
+ case(axios.AxiosError.ERR_BAD_REQUEST):
+ setErrorMessage("Acceso no autorizado");
+ break;
+ case(axios.AxiosError.ERR_NETWORK):
+ setErrorMessage("Conexión con el servidor fallida");
+ break;
+ }
+ }
+ throw new Error;
+ }
+ }
+ toast.promise(
+ fetch(),{
+ pending: "Subiendo datos...",
+ success: "Los datos se han subido correctamente",
+ error: errorMessage
+ }
+ )
+ }
+
+ const getTownsByState = async (stateId: number, stateName:string) => {
+ try{
+ const towns = await townRepository.getTownsByState(stateId, stateName);
+ setTownsList(towns);
+ }catch(error: any){
+
+ }
+ }
+
+ return {register, handleSubmit, errors, onSubmit, setValue, townsList, getTownsByState, setTownsList};
+}
\ No newline at end of file
diff --git a/web/src/hooks/useTownRegister.tsx b/web/src/hooks/useTownRegister.tsx
deleted file mode 100644
index 1c5bf209f5a02f4ce022c8b771b14f2db261aa57..0000000000000000000000000000000000000000
--- a/web/src/hooks/useTownRegister.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import { useEffect, useState } from "react";
-import { TownDatasourceProd } from "../data/datasources/prod/town_datasource"
-import { TownRepositoryProd } from "../data/repositories/prod/town_repository";
-import { State } from "../infraestructure/entities/state";
-import { SubmitHandler, useForm } from "react-hook-form";
-import { TownFormValues } from "../infraestructure/entities/town_form_values";
-
-const townDatasource = new TownDatasourceProd();
-const townRepository = new TownRepositoryProd(townDatasource);
-
-export const useTownRegister = () => {
- const [statesList, setStatesList] = useState(null);
- const {
- register,
- handleSubmit,
- setError,
- formState: {errors},
- setValue
- } = useForm();
-
- useEffect(() => {
- const getStates = async () => {
- const states = await townRepository.getStates();
- setStatesList(states);
- }
- getStates();
- }, []);
-
- const onSubmit: SubmitHandler = (data: TownFormValues) => {
- const fetch = async () => {
- try{
- await townRepository.registerTown(data);
- }catch(error: any){
-
- }
- }
- fetch();
- }
-
- return {statesList, register, handleSubmit, errors, onSubmit, setValue};
-}
\ No newline at end of file
diff --git a/web/src/hooks/useWindowShow.tsx b/web/src/hooks/useWindowShow.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..42883f23257c1f6b8b925d0924bf4bb1a1449253
--- /dev/null
+++ b/web/src/hooks/useWindowShow.tsx
@@ -0,0 +1,12 @@
+import { useState } from "react"
+
+export const useWindowShow = () => {
+ const [isWindowActive, setIsWindowActive] = useState(false);
+ const [renderCount, setRenderCount] = useState(0);
+
+ const forceRenderList = () =>{
+ setRenderCount(prevCount => prevCount + 1);
+ }
+
+ return {isWindowActive, setIsWindowActive, forceRenderList, renderCount};
+}
\ No newline at end of file
diff --git a/web/src/infraestructure/datasources/admin_datasource.ts b/web/src/infraestructure/datasources/admin_datasource.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1b6ca14376ae4fe704b3639e17313c0c5afd4595
--- /dev/null
+++ b/web/src/infraestructure/datasources/admin_datasource.ts
@@ -0,0 +1,5 @@
+import { AdminFormValues } from "../entities/admin_form_values";
+
+export interface AdminDatasourceInf{
+ registerAdmin(form: AdminFormValues): Promise;
+}
\ No newline at end of file
diff --git a/web/src/infraestructure/datasources/place_datasource.ts b/web/src/infraestructure/datasources/place_datasource.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a49a21284cea0f52e5c7f1f5b925c96c1f4e9146
--- /dev/null
+++ b/web/src/infraestructure/datasources/place_datasource.ts
@@ -0,0 +1,5 @@
+import { PlaceFormValues } from "../entities/place";
+
+export interface PlaceDatasourceInf{
+ registerPlace(form: PlaceFormValues): Promise;
+}
\ No newline at end of file
diff --git a/web/src/infraestructure/datasources/town_datasource.ts b/web/src/infraestructure/datasources/town_datasource.ts
index 229d5124b4e7d74595dd897d33bf7d8c126a5fef..7dfed2eb4d3d6cdc084768aa84a112ec02fc27ec 100644
--- a/web/src/infraestructure/datasources/town_datasource.ts
+++ b/web/src/infraestructure/datasources/town_datasource.ts
@@ -1,7 +1,8 @@
import { State } from "../entities/state";
-import { TownFormValues } from "../entities/town_form_values";
+import { Town, TownFormValues } from "../entities/town";
export interface TownDatasourceInf{
getStates(): Promise;
registerTown(form: TownFormValues): void;
+ getTownsByState(idState: number, stateName:string) : Promise;
}
\ No newline at end of file
diff --git a/web/src/infraestructure/entities/admin_form_values.ts b/web/src/infraestructure/entities/admin_form_values.ts
new file mode 100644
index 0000000000000000000000000000000000000000..09d39e2b94d929019f7f346bb96aef9e6e3c90f5
--- /dev/null
+++ b/web/src/infraestructure/entities/admin_form_values.ts
@@ -0,0 +1,7 @@
+export interface AdminFormValues {
+ email: string;
+ name: string;
+ password: string;
+ confirmPassword: string;
+ townAdmin: number;
+}
\ No newline at end of file
diff --git a/web/src/infraestructure/entities/image.ts b/web/src/infraestructure/entities/image.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a71477057c9ae91e2f1f1e527ac94e83148bc42d
--- /dev/null
+++ b/web/src/infraestructure/entities/image.ts
@@ -0,0 +1,4 @@
+export interface Image{
+ file: File;
+ preview: string;
+}
\ No newline at end of file
diff --git a/web/src/infraestructure/entities/place.ts b/web/src/infraestructure/entities/place.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6c9837621f23f0e46d3773315aa0bdeeda98540b
--- /dev/null
+++ b/web/src/infraestructure/entities/place.ts
@@ -0,0 +1,32 @@
+export interface PlaceFormValues{
+ idTown: number;
+ name: string;
+ descriptions: string[];
+ latitude: number;
+ longitude: number;
+ openAt: number;
+ closeAt: number;
+ available: AvailableDays;
+ imagesList: File[];
+ startDate?: Date;
+ endDate?: Date;
+}
+
+export enum AvailableDays {
+ WEEKEND = 'weekend',
+ ALL_DAYS = 'all_days',
+ WEEKDAYS = 'weekdays',
+ CUSTOM = 'custom'
+}
+
+export interface availableDaysOption{
+ option: AvailableDays;
+ name: string;
+}
+
+export const availableDaysList= [
+ { option: AvailableDays.WEEKEND, name: 'Fines de semana' },
+ { option: AvailableDays.ALL_DAYS, name: 'Todos los dias' },
+ { option: AvailableDays.WEEKDAYS, name: 'Lunes a Viernes' },
+ { option: AvailableDays.CUSTOM, name: 'Personalizado' }
+];
\ No newline at end of file
diff --git a/web/src/infraestructure/entities/state.ts b/web/src/infraestructure/entities/state.ts
index 7079ff293ca95fac28d82ebc3401243eddb15399..fd1cf8c82e53a4f9c8f898fb87491f109c1c682f 100644
--- a/web/src/infraestructure/entities/state.ts
+++ b/web/src/infraestructure/entities/state.ts
@@ -1,5 +1,5 @@
export interface State {
- stateId?: number,
- name?: string,
- imageURL?: string
+ stateId: number,
+ name: string,
+ imageURL: string
}
\ No newline at end of file
diff --git a/web/src/infraestructure/entities/town.ts b/web/src/infraestructure/entities/town.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e591aed3052b2a8b55637e2dac8494826d683b3d
--- /dev/null
+++ b/web/src/infraestructure/entities/town.ts
@@ -0,0 +1,17 @@
+export interface TownFormValues {
+ name : string;
+ descriptionES : string;
+ descriptionEN : string;
+ state : string;
+ imageURL : File;
+}
+
+export interface Town {
+ idTown: number;
+ name : string;
+ descriptionES? : string;
+ descriptionEN? : string;
+ idState : number;
+ state: string;
+ imageURL? : string;
+}
\ No newline at end of file
diff --git a/web/src/infraestructure/entities/town_form_values.ts b/web/src/infraestructure/entities/town_form_values.ts
deleted file mode 100644
index ffd11e36424a648627fa6b0ff01bc99346134c31..0000000000000000000000000000000000000000
--- a/web/src/infraestructure/entities/town_form_values.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export interface TownFormValues {
- name : string
- descriptionES : string
- descriptionEN : string
- state : string
- imageURL : File
-}
\ No newline at end of file
diff --git a/web/src/infraestructure/entities/user.ts b/web/src/infraestructure/entities/user.ts
index a600d77e58cefc74ca065fc6513610a2b847e47f..91c4545caf844ade937b960217b30afab5de9f91 100644
--- a/web/src/infraestructure/entities/user.ts
+++ b/web/src/infraestructure/entities/user.ts
@@ -1,7 +1,7 @@
import { UserRole } from "../../constants/roles";
export interface UserEntity {
- name?: string;
+ name: string;
lastName?: string;
email: string;
role: UserRole;
@@ -16,4 +16,174 @@ export interface UserInfo {
name?: string;
lastName?: string;
email: string;
-}
\ No newline at end of file
+}
+
+export class Convert {
+ public static toUser(json: string): UserEntity {
+ return cast(JSON.parse(json), r("User"));
+ }
+
+ public static userToJson(value: UserEntity): string {
+ return JSON.stringify(uncast(value, r("User")), null, 2);
+ }
+}
+
+function invalidValue(typ: any, val: any, key: any, parent: any = ''): never {
+ const prettyTyp = prettyTypeName(typ);
+ const parentText = parent ? ` on ${parent}` : '';
+ const keyText = key ? ` for key "${key}"` : '';
+ throw Error(`Invalid value${keyText}${parentText}. Expected ${prettyTyp} but got ${JSON.stringify(val)}`);
+}
+
+function prettyTypeName(typ: any): string {
+ if (Array.isArray(typ)) {
+ if (typ.length === 2 && typ[0] === undefined) {
+ return `an optional ${prettyTypeName(typ[1])}`;
+ } else {
+ return `one of [${typ.map(a => { return prettyTypeName(a); }).join(", ")}]`;
+ }
+ } else if (typeof typ === "object" && typ.literal !== undefined) {
+ return typ.literal;
+ } else {
+ return typeof typ;
+ }
+}
+
+function jsonToJSProps(typ: any): any {
+ if (typ.jsonToJS === undefined) {
+ const map: any = {};
+ typ.props.forEach((p: any) => map[p.json] = { key: p.js, typ: p.typ });
+ typ.jsonToJS = map;
+ }
+ return typ.jsonToJS;
+}
+
+function jsToJSONProps(typ: any): any {
+ if (typ.jsToJSON === undefined) {
+ const map: any = {};
+ typ.props.forEach((p: any) => map[p.js] = { key: p.json, typ: p.typ });
+ typ.jsToJSON = map;
+ }
+ return typ.jsToJSON;
+}
+
+function transform(val: any, typ: any, getProps: any, key: any = '', parent: any = ''): any {
+ function transformPrimitive(typ: string, val: any): any {
+ if (typeof typ === typeof val) return val;
+ return invalidValue(typ, val, key, parent);
+ }
+
+ function transformUnion(typs: any[], val: any): any {
+ // val must validate against one typ in typs
+ const l = typs.length;
+ for (let i = 0; i < l; i++) {
+ const typ = typs[i];
+ try {
+ return transform(val, typ, getProps);
+ } catch (_) {}
+ }
+ return invalidValue(typs, val, key, parent);
+ }
+
+ function transformEnum(cases: string[], val: any): any {
+ if (cases.indexOf(val) !== -1) return val;
+ return invalidValue(cases.map(a => { return l(a); }), val, key, parent);
+ }
+
+ function transformArray(typ: any, val: any): any {
+ // val must be an array with no invalid elements
+ if (!Array.isArray(val)) return invalidValue(l("array"), val, key, parent);
+ return val.map(el => transform(el, typ, getProps));
+ }
+
+ function transformDate(val: any): any {
+ if (val === null) {
+ return null;
+ }
+ const d = new Date(val);
+ if (isNaN(d.valueOf())) {
+ return invalidValue(l("Date"), val, key, parent);
+ }
+ return d;
+ }
+
+ function transformObject(props: { [k: string]: any }, additional: any, val: any): any {
+ if (val === null || typeof val !== "object" || Array.isArray(val)) {
+ return invalidValue(l(ref || "object"), val, key, parent);
+ }
+ const result: any = {};
+ Object.getOwnPropertyNames(props).forEach(key => {
+ const prop = props[key];
+ const v = Object.prototype.hasOwnProperty.call(val, key) ? val[key] : undefined;
+ result[prop.key] = transform(v, prop.typ, getProps, key, ref);
+ });
+ Object.getOwnPropertyNames(val).forEach(key => {
+ if (!Object.prototype.hasOwnProperty.call(props, key)) {
+ result[key] = transform(val[key], additional, getProps, key, ref);
+ }
+ });
+ return result;
+ }
+
+ if (typ === "any") return val;
+ if (typ === null) {
+ if (val === null) return val;
+ return invalidValue(typ, val, key, parent);
+ }
+ if (typ === false) return invalidValue(typ, val, key, parent);
+ let ref: any = undefined;
+ while (typeof typ === "object" && typ.ref !== undefined) {
+ ref = typ.ref;
+ typ = typeMap[typ.ref];
+ }
+ if (Array.isArray(typ)) return transformEnum(typ, val);
+ if (typeof typ === "object") {
+ return typ.hasOwnProperty("unionMembers") ? transformUnion(typ.unionMembers, val)
+ : typ.hasOwnProperty("arrayItems") ? transformArray(typ.arrayItems, val)
+ : typ.hasOwnProperty("props") ? transformObject(getProps(typ), typ.additional, val)
+ : invalidValue(typ, val, key, parent);
+ }
+ // Numbers can be parsed by Date but shouldn't be.
+ if (typ === Date && typeof val !== "number") return transformDate(val);
+ return transformPrimitive(typ, val);
+}
+
+function cast(val: any, typ: any): T {
+ return transform(val, typ, jsonToJSProps);
+}
+
+function uncast(val: T, typ: any): any {
+ return transform(val, typ, jsToJSONProps);
+}
+
+function l(typ: any) {
+ return { literal: typ };
+}
+
+function a(typ: any) {
+ return { arrayItems: typ };
+}
+
+function u(...typs: any[]) {
+ return { unionMembers: typs };
+}
+
+function o(props: any[], additional: any) {
+ return { props, additional };
+}
+
+function m(additional: any) {
+ return { props: [], additional };
+}
+
+function r(name: string) {
+ return { ref: name };
+}
+
+const typeMap: any = {
+ "User": o([
+ { json: "email", js: "email", typ: "" },
+ { json: "name", js: "name", typ: "" },
+ { json: "role", js: "role", typ: "" },
+ ], false),
+};
diff --git a/web/src/infraestructure/repositories/admin_repository.ts b/web/src/infraestructure/repositories/admin_repository.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c4af71abe1f117d8fa548d8513f1aabe0e2b1c86
--- /dev/null
+++ b/web/src/infraestructure/repositories/admin_repository.ts
@@ -0,0 +1,5 @@
+import { AdminFormValues } from "../entities/admin_form_values";
+
+export interface AdminRepositoryInf{
+ registerAdmin(form: AdminFormValues): void;
+}
\ No newline at end of file
diff --git a/web/src/infraestructure/repositories/place_repository.ts b/web/src/infraestructure/repositories/place_repository.ts
new file mode 100644
index 0000000000000000000000000000000000000000..42267ef19f8c6f5c5ce10d24598e74745fabe068
--- /dev/null
+++ b/web/src/infraestructure/repositories/place_repository.ts
@@ -0,0 +1,5 @@
+import { PlaceFormValues } from "../entities/place";
+
+export interface PlaceRepositoryInf{
+ registerPlace(form: PlaceFormValues): Promise;
+}
\ No newline at end of file
diff --git a/web/src/infraestructure/repositories/town_repository.ts b/web/src/infraestructure/repositories/town_repository.ts
index 6a919e9f31a2aed31cb5b221b439be2bc8b9d091..1c7388c201cdd5ddc99663f64ea039690a8195c9 100644
--- a/web/src/infraestructure/repositories/town_repository.ts
+++ b/web/src/infraestructure/repositories/town_repository.ts
@@ -1,7 +1,8 @@
import { State } from "../entities/state";
-import { TownFormValues } from "../entities/town_form_values";
+import { Town, TownFormValues } from "../entities/town";
export interface TownRepositoryInf{
getStates(): Promise;
- registerTown(form: TownFormValues): void;
+ registerTown(form: TownFormValues): Promise;
+ getTownsByState(idState: number, stateName:string) : Promise;
}
\ No newline at end of file
diff --git a/web/src/pages/home/admin_page/admin_home_page.tsx b/web/src/pages/home/admin_page/admin_home_page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..54eed2254fc567288880e6b698a009ce2f667c43
--- /dev/null
+++ b/web/src/pages/home/admin_page/admin_home_page.tsx
@@ -0,0 +1,65 @@
+import { Menu, MenuItem, Sidebar } from "react-pro-sidebar"
+import './assets/styles/style.css';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faLocation, faMonument, faPlaceOfWorship, faUserTie } from "@fortawesome/free-solid-svg-icons";
+import { useState } from "react";
+import { AdminPanelNavBar } from "../../../components/admin_panel_navbar/admin_navbar";
+import { SidebarHeader } from "../../../components/sidebar_header/sidebar_header";
+import { AdminPanelPlaceScreen } from "../../../components/admin_panel_places/admin_panel_place_screen/admin_panel_place_screen";
+import { AdminSelectedPanel } from "../../../constants/selected_panel";
+import { useWindowShow } from "../../../hooks/useWindowShow";
+
+export const AdminHomePage = () => {
+ const [collapsed, setCollapsed] = useState(true);
+ const {setIsWindowActive, isWindowActive} = useWindowShow();
+ const [selectedPanel, setSelectedPanel] = useState(AdminSelectedPanel.PLACES);
+
+ return (
+
+
{
+ isWindowActive ?
+ setCollapsed(true)
+ :
+ setCollapsed(false)
+ }}
+ onMouseOut={() => setCollapsed(true)}
+
+ >
+
+
+
+
+
+
+ {(() => {
+ switch (selectedPanel) {
+ case AdminSelectedPanel.PLACES:
+ return
;
+ case AdminSelectedPanel.ACTIVITIES:
+ return
+ default:
+ return null;
+ }
+ })()}
+
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/web/src/pages/home/admin_page/assets/styles/style.css b/web/src/pages/home/admin_page/assets/styles/style.css
new file mode 100644
index 0000000000000000000000000000000000000000..0e22ba9f21fa8801ea3a6e1f69f7230050f99b88
--- /dev/null
+++ b/web/src/pages/home/admin_page/assets/styles/style.css
@@ -0,0 +1,32 @@
+:root {
+ --shadow: 0px 2px 8px 0px gray;
+}
+
+
+.admin-panel-root{
+ display: flex;
+ height: 100vh;
+ max-height: 100vh;
+ width: 100vw;
+ max-width: 100vw;
+}
+
+
+.admin-panel-body{
+ flex-grow: 1;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ background: #ECEAFF;
+}
+
+.admin-panel-content{
+ max-height: 84vh;
+ min-height: 84vh;
+ display: flex;
+}
+
+.footer-cnt{
+ height: 10vh;
+ background: #ECEAFF;
+}
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
index 3ccfed0bd768c9376c8187c9dc0d7035396b9562..70256ece2c8703858702914a1e790f0c3cc768f8 100644
--- a/web/src/pages/home/super_admin_page/assets/styles/style.css
+++ b/web/src/pages/home/super_admin_page/assets/styles/style.css
@@ -5,15 +5,28 @@
.superdmin-panel-root{
display: flex;
- height: 100%;
- width: 100%;
+ height: 100vh;
+ max-height: 100vh;
+ width: 100vw;
+ max-width: 100vw;
}
.superadmin-panel-body{
- height: 100vh;
flex-grow: 1;
+ height: 100%;
display: flex;
flex-direction: column;
background: #ECEAFF;
}
+
+.superadmin-panel-content{
+ max-height: 84vh;
+ min-height: 84vh;
+ display: flex;
+}
+
+.footer-cnt{
+ height: 10vh;
+ 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
index 8fc5769f514fddd4de374f892df09b508d692a56..59645e97c2c55d01d69ee0e49a3693c2623ead3f 100644
--- 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
@@ -1,17 +1,36 @@
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 { faMonument, faUserTie } from "@fortawesome/free-solid-svg-icons";
+import { useEffect, useState } from "react";
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";
+import { useGetStatesList } from "../../../hooks/useGetStatesList";
+import axios from "axios";
+import { LoadingScreen } from "../../../components/loading_screen/loading_screen";
export const SuperAdminHomePage = () => {
const [collapsed, setCollapsed] = useState(true);
const [windowActive, setWindowActive] = useState(false);
const [townPanel, setTownPanel] = useState(true);
+ const [isLoading, setIsLoading] = useState(true);
+ const {getStates, statesList} = useGetStatesList();
+
+ useEffect(() => {
+ const getStatesList = async () => {
+ try{
+ getStates();
+ setIsLoading(false);
+ }catch(error: any){
+ if(axios.isAxiosError(error)){
+ //console.log(error)
+ }
+ }
+ }
+ getStatesList();
+ }, []);
return (
@@ -45,12 +64,25 @@ export const SuperAdminHomePage = () => {
{townPanel
?
+ isLoading || statesList.length===0
+ ?
+
+ :
+ windowActive={windowActive}
+ setWindowActive={setWindowActive}
+ statesList={statesList}
+ />
:
- }
+
+ }
+
+
+
diff --git a/web/src/pages/login_page/login_page.tsx b/web/src/pages/login_page/login_page.tsx
index 6cbc8ce23e8be06cd73e1c18f072edeef7a7b4ab..2d587c7a226740753bdb787563e11935a49a160c 100644
--- a/web/src/pages/login_page/login_page.tsx
+++ b/web/src/pages/login_page/login_page.tsx
@@ -1,8 +1,10 @@
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 { faCircleExclamation, faEye , faEyeSlash, faWindowClose} from "@fortawesome/free-solid-svg-icons";
import { useLogin } from "../../hooks/useLogin";
+import { useState } from "react";
+import { LoadingScreen } from "../../components/loading_screen/loading_screen";
export const LoginPage = () => {
const {
@@ -11,15 +13,38 @@ export const LoginPage = () => {
handleMouseDownPassword,
} = usePasswoordVisibility();
+ const [isLoading, setIsLoading] = useState(false);
+
const {
register,
handleSubmit,
errors,
- onSubmit
- } = useLogin();
+ onSubmit,
+ clearErrors,
+ } = useLogin(setIsLoading);
return (
+ {errors.root?.serverError.type==500 &&
+
+
+
+ Error
+
+
{clearErrors("root.serverError")}}
+ />
+
+
+
+ {errors.root?.serverError.message}
+
+
+ }
+
+ {isLoading &&
}
diff --git a/web/src/pages/login_page/styles/styles.css b/web/src/pages/login_page/styles/styles.css
index 1d17cb075be9456b0ec4123e256e7057ca4e6116..d55bf18d22702116095b5e1183b1ba5adf886eef 100644
--- a/web/src/pages/login_page/styles/styles.css
+++ b/web/src/pages/login_page/styles/styles.css
@@ -1,87 +1,87 @@
@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;
+ 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;
+ 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);
+ 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))
+ 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;
+ padding: 10px 40px 50px 30px;
}
.login-form-container .login-form-field{
- position: relative;
- height: 50px;
- width: 100%;
- margin-top: 20px;
+ 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;
+ 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;
+ 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%);
+ 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;
+ position: absolute;
+ top: 50%;
+ right: 5px;
+ transform: translateY(-50%);
+ transform: all 0.3s ease;
+ cursor: pointer;
}
@@ -89,34 +89,133 @@ 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%);
+ 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;
+ 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;
+ 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
+ transform: scale(0.95);
+}
+
+.login_error{
+ color: red;
+ font-size: 12px;
+ padding: 0;
+ margin: 0;
+}
+
+.server-error-window {
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ margin: auto;
+ width: 500px;
+ height: 200px;
+ z-index: 2;
+ box-shadow: 0px 15px 20px rgba(0,0,0,0.1);
+ background-color: red;
+ display: flex;
+ flex-direction: column;
+ font-size: 30px;
+ font-weight: 500;
+ line-height: 50px;
+ text-align: center;
+ user-select: none;
+}
+
+.window-error-header{
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ color: white;
+ justify-content: center;
+ height: 20%;
+}
+
+.window-title{
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+}
+
+.window-title div{
+ color: white;
+}
+
+.window-close-button{
+ color: white;
+ display: inline-block;
+ cursor: pointer;
+ position: absolute;
+ right: 5px;
+}
+
+.window-error-body{
+ display: flex;
+ flex-grow: 1;
+ background: white;
+ align-items: center;
+ justify-content: center;
+ height: 80%;
+ font-size: 25px;
+ font-weight: 400;
+ line-height: 50px;
+}
+
+.circle-exclamation-icon{
+ color: red;
+ height: 50px;
+ margin: 20px;
+}
+
+.loading_spinner{
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ margin: auto;
+ z-index: 3;
+ width: 150px;
+ padding: 20px;
+ aspect-ratio: 1;
+ border-radius: 50%;
+ background: #25b09b;
+ --_m:
+ conic-gradient(#0000 10%,#000),
+ linear-gradient(#000 0 0) content-box;
+ -webkit-mask: var(--_m);
+ mask: var(--_m);
+ -webkit-mask-composite: source-out;
+ mask-composite: subtract;
+ animation: l3 1s infinite linear;
+}
+@keyframes l3 {to{transform: rotate(1turn)}}
\ No newline at end of file
diff --git a/web/src/router/protected_route.tsx b/web/src/router/protected_route.tsx
index 2a412f384bcbe251c3bdef958253be25d3318984..f816c8d42ec3c4b7467f14dd646e53476306810b 100644
--- a/web/src/router/protected_route.tsx
+++ b/web/src/router/protected_route.tsx
@@ -1,15 +1,27 @@
import { Navigate, Outlet } from "react-router-dom";
import { useAuth } from "../context/auth_context";
+import { UserRole } from "../constants/roles";
+import { AdminHomePage } from "../pages/home/admin_page/admin_home_page";
+import { SuperAdminHomePage } from "../pages/home/super_admin_page/super_admin_home_page";
interface ProtectedRouteProps {
- allowedRoles?: string[];
+ allowedRole: UserRole;
}
-export const ProtectedRoute = ({allowedRoles}: ProtectedRouteProps) => {
- const {user} = useAuth();
- if(!user){
- return
;
+export const ProtectedRoute = () => {
+ const {user, logout} = useAuth();
+ if(!user){
+ return ;
+ }else{
+ if(user.role==UserRole.ADMIN){
+ return ;
+ }else if(user.role===UserRole.SUPERADMIN){
+ return ;
+ }else{
+ logout();
+ return ;
}
+ }
- return ;
+ return ;
}
\ No newline at end of file
diff --git a/web/src/router/router.tsx b/web/src/router/router.tsx
index 9e051b3af36d2dadde7c7e533ad005fa60446b03..ca5f6fe8e5085ca694037797d82018f2b60903c6 100644
--- a/web/src/router/router.tsx
+++ b/web/src/router/router.tsx
@@ -1,17 +1,18 @@
import { createBrowserRouter } from "react-router-dom";
-import { SUPERADMIN_ROLES, ADMIN_ROLES } from "../constants/roles";
+import { SUPERADMIN_ROLE, ADMIN_ROLE } 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";
+import { AdminHomePage } from "../pages/home/admin_page/admin_home_page";
export const router = createBrowserRouter([
{
- element: ,
+ element: ,
children: [
{
index: true,
path: "/",
- element:
+ element:
}
]
},