From f1e184383d704450f0fcb573d5dadab2e6e4ad45 Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Sat, 17 Jun 2023 15:49:12 +0800 Subject: [PATCH 01/35] added mui stylistics for client login --- package-lock.json | 1015 +++++++++++++++++++++++++++- package.json | 6 + src/App.css | 12 +- src/App.js | 17 +- src/components/AdminUpload.js | 0 src/components/ImageTile.js | 100 +++ src/components/ImgDownload.js | 38 ++ src/components/ResponsiveAppBar.js | 167 +++++ src/components/SearchBar.js | 31 + 9 files changed, 1361 insertions(+), 25 deletions(-) create mode 100644 src/components/AdminUpload.js create mode 100644 src/components/ImageTile.js create mode 100644 src/components/ImgDownload.js create mode 100644 src/components/ResponsiveAppBar.js create mode 100644 src/components/SearchBar.js diff --git a/package-lock.json b/package-lock.json index 147f7995..338a04b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,12 @@ "name": "project2-bootcamp", "version": "0.1.0", "dependencies": { + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", + "@mui/icons-material": "^5.11.16", + "@mui/material": "^5.13.5", + "file-saver": "^2.0.5", + "jszip": "^3.10.1", "react": "^18.1.0", "react-dom": "^18.1.0", "react-scripts": "5.0.1" @@ -1764,11 +1770,11 @@ } }, "node_modules/@babel/runtime": { - "version": "7.17.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz", - "integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", + "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", "dependencies": { - "regenerator-runtime": "^0.13.4" + "regenerator-runtime": "^0.13.11" }, "engines": { "node": ">=6.9.0" @@ -1997,6 +2003,158 @@ "postcss": "^8.3" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", + "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/serialize": "^1.1.2", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", + "dependencies": { + "@emotion/memoize": "^0.8.1", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", + "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", + "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/react": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz", + "integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/cache": "^11.11.0", + "@emotion/serialize": "^1.1.2", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz", + "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==", + "dependencies": { + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/unitless": "^0.8.1", + "@emotion/utils": "^1.2.1", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" + }, + "node_modules/@emotion/styled": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz", + "integrity": "sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/is-prop-valid": "^1.2.1", + "@emotion/serialize": "^1.1.2", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", + "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" + }, "node_modules/@eslint/eslintrc": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.2.tgz", @@ -2785,6 +2943,262 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.3.tgz", "integrity": "sha512-nkalE/f1RvRGChwBnEIoBfSEYOXnCRdleKuv6+lePbMDrMZXeDQnqak5XDOeBgrPPyPfAdcCu/B5z+v3VhplGg==" }, + "node_modules/@mui/base": { + "version": "5.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.4.tgz", + "integrity": "sha512-ejhtqYJpjDgHGEljjMBQWZ22yEK0OzIXNa7toJmmXsP4TT3W7xVy8bTJ0TniPDf+JNjrsgfgiFTDGdlEhV1E+g==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@emotion/is-prop-valid": "^1.2.1", + "@mui/types": "^7.2.4", + "@mui/utils": "^5.13.1", + "@popperjs/core": "^2.11.8", + "clsx": "^1.2.1", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/base/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "5.13.4", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.13.4.tgz", + "integrity": "sha512-yFrMWcrlI0TqRN5jpb6Ma9iI7sGTHpytdzzL33oskFHNQ8UgrtPas33Y1K7sWAMwCrr1qbWDrOHLAQG4tAzuSw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + } + }, + "node_modules/@mui/icons-material": { + "version": "5.11.16", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.11.16.tgz", + "integrity": "sha512-oKkx9z9Kwg40NtcIajF9uOXhxiyTZrrm9nmIJ4UjkU2IdHpd4QVLbCc/5hZN/y0C6qzi2Zlxyr9TGddQx2vx2A==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "5.13.5", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.13.5.tgz", + "integrity": "sha512-eMay+Ue1OYXOFMQA5Aau7qbAa/kWHLAyi0McsbPTWssCbGehqkF6CIdPsfVGw6tlO+xPee1hUitphHJNL3xpOQ==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@mui/base": "5.0.0-beta.4", + "@mui/core-downloads-tracker": "^5.13.4", + "@mui/system": "^5.13.5", + "@mui/types": "^7.2.4", + "@mui/utils": "^5.13.1", + "@types/react-transition-group": "^4.4.6", + "clsx": "^1.2.1", + "csstype": "^3.1.2", + "prop-types": "^15.8.1", + "react-is": "^18.2.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/@mui/private-theming": { + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.13.1.tgz", + "integrity": "sha512-HW4npLUD9BAkVppOUZHeO1FOKUJWAwbpy0VQoGe3McUYTlck1HezGHQCfBQ5S/Nszi7EViqiimECVl9xi+/WjQ==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@mui/utils": "^5.13.1", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "5.13.2", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.13.2.tgz", + "integrity": "sha512-VCYCU6xVtXOrIN8lcbuPmoG+u7FYuOERG++fpY74hPpEWkyFQG97F+/XfTQVYzlR2m7nPjnwVUgATcTCMEaMvw==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@emotion/cache": "^11.11.0", + "csstype": "^3.1.2", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "5.13.5", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.13.5.tgz", + "integrity": "sha512-n0gzUxoZ2ZHZgnExkh2Htvo9uW2oakofgPRQrDoa/GQOWyRD0NH9MDszBwOb6AAoXZb+OV5TE7I4LeZ/dzgHYA==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@mui/private-theming": "^5.13.1", + "@mui/styled-engine": "^5.13.2", + "@mui/types": "^7.2.4", + "@mui/utils": "^5.13.1", + "clsx": "^1.2.1", + "csstype": "^3.1.2", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.4.tgz", + "integrity": "sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA==", + "peerDependencies": { + "@types/react": "*" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.13.1.tgz", + "integrity": "sha512-6lXdWwmlUbEU2jUI8blw38Kt+3ly7xkmV9ljzY4Q20WhsJMWiNry9CX8M+TaP/HbtuyR8XKsdMgQW7h7MM3n3A==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@types/prop-types": "^15.7.5", + "@types/react-is": "^18.2.0", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0" + } + }, + "node_modules/@mui/utils/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2866,6 +3280,15 @@ } } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -3387,6 +3810,11 @@ "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.6.0.tgz", "integrity": "sha512-G/AdOadiZhnJp0jXCaBQU449W2h716OW/EoXeYkCytxKL06X1WCXB4DZpp8TpZ8eyIJVS1cw4lrlkkSYU21cDw==" }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, "node_modules/@types/q": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", @@ -3402,6 +3830,32 @@ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, + "node_modules/@types/react": { + "version": "18.2.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.12.tgz", + "integrity": "sha512-ndmBMLCgn38v3SntMeoJaIrO6tGHYKMEBohCUmw8HoLLQdRMOIGXfeYaBTLe2lsFaSB3MOK1VXscYFnmLtTSmw==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-1vz2yObaQkLL7YFe/pme2cpvDsCwI1WXIfL+5eLz0MI9gFG24Re16RzUsI8t9XZn9ZWvgLNDrJBmrqXJO7GNQQ==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", + "integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -3415,6 +3869,11 @@ "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" }, + "node_modules/@types/scheduler": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", + "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" + }, "node_modules/@types/serve-index": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", @@ -4920,6 +5379,14 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -5586,6 +6053,11 @@ "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" }, + "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==" + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -5829,6 +6301,15 @@ "utila": "~0.4" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -7046,6 +7527,11 @@ "webpack": "^4.0.0 || ^5.0.0" } }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" + }, "node_modules/filelist": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.3.tgz", @@ -7138,6 +7624,11 @@ "url": "https://github.com/avajs/find-cache-dir?sponsor=1" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -7705,6 +8196,19 @@ "he": "bin/he" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -7967,6 +8471,11 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "node_modules/immer": { "version": "9.0.12", "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.12.tgz", @@ -10571,6 +11080,39 @@ "node": ">=4.0" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -10628,6 +11170,14 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lilconfig": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.5.tgz", @@ -11348,6 +11898,11 @@ "node": ">=6" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -13184,6 +13739,21 @@ } } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -13247,9 +13817,9 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, "node_modules/regenerator-transform": { "version": "0.15.0", @@ -13848,6 +14418,11 @@ "node": ">= 0.8.0" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -14246,6 +14821,11 @@ "postcss": "^8.2.15" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -17044,11 +17624,11 @@ } }, "@babel/runtime": { - "version": "7.17.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz", - "integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", + "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", "requires": { - "regenerator-runtime": "^0.13.4" + "regenerator-runtime": "^0.13.11" } }, "@babel/runtime-corejs3": { @@ -17187,6 +17767,132 @@ "integrity": "sha512-T5ZyNSw9G0x0UDFiXV40a7VjKw2b+l4G+S0sctKqxhx8cg9QtMUAGwJBVU9mHPDPoZEmwm0tEoukjl4zb9MU7Q==", "requires": {} }, + "@emotion/babel-plugin": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", + "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", + "requires": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/serialize": "^1.1.2", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" + } + } + }, + "@emotion/cache": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", + "requires": { + "@emotion/memoize": "^0.8.1", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "stylis": "4.2.0" + } + }, + "@emotion/hash": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", + "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" + }, + "@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==", + "requires": { + "@emotion/memoize": "^0.8.1" + } + }, + "@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "@emotion/react": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz", + "integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==", + "requires": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/cache": "^11.11.0", + "@emotion/serialize": "^1.1.2", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "hoist-non-react-statics": "^3.3.1" + } + }, + "@emotion/serialize": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz", + "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==", + "requires": { + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/unitless": "^0.8.1", + "@emotion/utils": "^1.2.1", + "csstype": "^3.0.2" + } + }, + "@emotion/sheet": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" + }, + "@emotion/styled": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz", + "integrity": "sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==", + "requires": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/is-prop-valid": "^1.2.1", + "@emotion/serialize": "^1.1.2", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1" + } + }, + "@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, + "@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", + "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", + "requires": {} + }, + "@emotion/utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" + }, + "@emotion/weak-memoize": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" + }, "@eslint/eslintrc": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.2.tgz", @@ -17771,6 +18477,128 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.3.tgz", "integrity": "sha512-nkalE/f1RvRGChwBnEIoBfSEYOXnCRdleKuv6+lePbMDrMZXeDQnqak5XDOeBgrPPyPfAdcCu/B5z+v3VhplGg==" }, + "@mui/base": { + "version": "5.0.0-beta.4", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.4.tgz", + "integrity": "sha512-ejhtqYJpjDgHGEljjMBQWZ22yEK0OzIXNa7toJmmXsP4TT3W7xVy8bTJ0TniPDf+JNjrsgfgiFTDGdlEhV1E+g==", + "requires": { + "@babel/runtime": "^7.21.0", + "@emotion/is-prop-valid": "^1.2.1", + "@mui/types": "^7.2.4", + "@mui/utils": "^5.13.1", + "@popperjs/core": "^2.11.8", + "clsx": "^1.2.1", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + }, + "dependencies": { + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + } + } + }, + "@mui/core-downloads-tracker": { + "version": "5.13.4", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.13.4.tgz", + "integrity": "sha512-yFrMWcrlI0TqRN5jpb6Ma9iI7sGTHpytdzzL33oskFHNQ8UgrtPas33Y1K7sWAMwCrr1qbWDrOHLAQG4tAzuSw==" + }, + "@mui/icons-material": { + "version": "5.11.16", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.11.16.tgz", + "integrity": "sha512-oKkx9z9Kwg40NtcIajF9uOXhxiyTZrrm9nmIJ4UjkU2IdHpd4QVLbCc/5hZN/y0C6qzi2Zlxyr9TGddQx2vx2A==", + "requires": { + "@babel/runtime": "^7.21.0" + } + }, + "@mui/material": { + "version": "5.13.5", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.13.5.tgz", + "integrity": "sha512-eMay+Ue1OYXOFMQA5Aau7qbAa/kWHLAyi0McsbPTWssCbGehqkF6CIdPsfVGw6tlO+xPee1hUitphHJNL3xpOQ==", + "requires": { + "@babel/runtime": "^7.21.0", + "@mui/base": "5.0.0-beta.4", + "@mui/core-downloads-tracker": "^5.13.4", + "@mui/system": "^5.13.5", + "@mui/types": "^7.2.4", + "@mui/utils": "^5.13.1", + "@types/react-transition-group": "^4.4.6", + "clsx": "^1.2.1", + "csstype": "^3.1.2", + "prop-types": "^15.8.1", + "react-is": "^18.2.0", + "react-transition-group": "^4.4.5" + }, + "dependencies": { + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + } + } + }, + "@mui/private-theming": { + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.13.1.tgz", + "integrity": "sha512-HW4npLUD9BAkVppOUZHeO1FOKUJWAwbpy0VQoGe3McUYTlck1HezGHQCfBQ5S/Nszi7EViqiimECVl9xi+/WjQ==", + "requires": { + "@babel/runtime": "^7.21.0", + "@mui/utils": "^5.13.1", + "prop-types": "^15.8.1" + } + }, + "@mui/styled-engine": { + "version": "5.13.2", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.13.2.tgz", + "integrity": "sha512-VCYCU6xVtXOrIN8lcbuPmoG+u7FYuOERG++fpY74hPpEWkyFQG97F+/XfTQVYzlR2m7nPjnwVUgATcTCMEaMvw==", + "requires": { + "@babel/runtime": "^7.21.0", + "@emotion/cache": "^11.11.0", + "csstype": "^3.1.2", + "prop-types": "^15.8.1" + } + }, + "@mui/system": { + "version": "5.13.5", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.13.5.tgz", + "integrity": "sha512-n0gzUxoZ2ZHZgnExkh2Htvo9uW2oakofgPRQrDoa/GQOWyRD0NH9MDszBwOb6AAoXZb+OV5TE7I4LeZ/dzgHYA==", + "requires": { + "@babel/runtime": "^7.21.0", + "@mui/private-theming": "^5.13.1", + "@mui/styled-engine": "^5.13.2", + "@mui/types": "^7.2.4", + "@mui/utils": "^5.13.1", + "clsx": "^1.2.1", + "csstype": "^3.1.2", + "prop-types": "^15.8.1" + } + }, + "@mui/types": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.4.tgz", + "integrity": "sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA==", + "requires": {} + }, + "@mui/utils": { + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.13.1.tgz", + "integrity": "sha512-6lXdWwmlUbEU2jUI8blw38Kt+3ly7xkmV9ljzY4Q20WhsJMWiNry9CX8M+TaP/HbtuyR8XKsdMgQW7h7MM3n3A==", + "requires": { + "@babel/runtime": "^7.21.0", + "@types/prop-types": "^15.7.5", + "@types/react-is": "^18.2.0", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + }, + "dependencies": { + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + } + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -17810,6 +18638,11 @@ "source-map": "^0.7.3" } }, + "@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" + }, "@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -18201,6 +19034,11 @@ "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.6.0.tgz", "integrity": "sha512-G/AdOadiZhnJp0jXCaBQU449W2h716OW/EoXeYkCytxKL06X1WCXB4DZpp8TpZ8eyIJVS1cw4lrlkkSYU21cDw==" }, + "@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, "@types/q": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", @@ -18216,6 +19054,32 @@ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, + "@types/react": { + "version": "18.2.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.12.tgz", + "integrity": "sha512-ndmBMLCgn38v3SntMeoJaIrO6tGHYKMEBohCUmw8HoLLQdRMOIGXfeYaBTLe2lsFaSB3MOK1VXscYFnmLtTSmw==", + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-1vz2yObaQkLL7YFe/pme2cpvDsCwI1WXIfL+5eLz0MI9gFG24Re16RzUsI8t9XZn9ZWvgLNDrJBmrqXJO7GNQQ==", + "requires": { + "@types/react": "*" + } + }, + "@types/react-transition-group": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", + "integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==", + "requires": { + "@types/react": "*" + } + }, "@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -18229,6 +19093,11 @@ "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" }, + "@types/scheduler": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", + "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" + }, "@types/serve-index": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", @@ -19343,6 +20212,11 @@ "wrap-ansi": "^7.0.0" } }, + "clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==" + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -19809,6 +20683,11 @@ } } }, + "csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, "damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -19990,6 +20869,15 @@ "utila": "~0.4" } }, + "dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -20883,6 +21771,11 @@ "schema-utils": "^3.0.0" } }, + "file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" + }, "filelist": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.3.tgz", @@ -20961,6 +21854,11 @@ "pkg-dir": "^4.1.0" } }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -21341,6 +22239,21 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, "hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -21536,6 +22449,11 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==" }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "immer": { "version": "9.0.12", "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.12.tgz", @@ -23414,6 +24332,41 @@ "object.assign": "^4.1.2" } }, + "jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -23456,6 +24409,14 @@ "type-check": "~0.4.0" } }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "requires": { + "immediate": "~3.0.5" + } + }, "lilconfig": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.5.tgz", @@ -23976,6 +24937,11 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, "param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -25174,6 +26140,17 @@ "workbox-webpack-plugin": "^6.4.1" } }, + "react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "requires": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + } + }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -25224,9 +26201,9 @@ } }, "regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, "regenerator-transform": { "version": "0.15.0", @@ -25657,6 +26634,11 @@ "send": "0.18.0" } }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -25955,6 +26937,11 @@ "postcss-selector-parser": "^6.0.4" } }, + "stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", diff --git a/package.json b/package.json index e58f30a2..1b3aea7b 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,12 @@ "version": "0.1.0", "private": true, "dependencies": { + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", + "@mui/icons-material": "^5.11.16", + "@mui/material": "^5.13.5", + "file-saver": "^2.0.5", + "jszip": "^3.10.1", "react": "^18.1.0", "react-dom": "^18.1.0", "react-scripts": "5.0.1" diff --git a/src/App.css b/src/App.css index 97b7c578..1b586f4c 100644 --- a/src/App.css +++ b/src/App.css @@ -2,18 +2,16 @@ text-align: center; } -.App-logo { - height: 40vmin; - pointer-events: none; -} - .App-header { - background-color: #282c34; + background-color: white; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; - font-size: calc(10px + 2vmin); color: white; } + +.Gallery-img{ + margin: 0 1%; +} \ No newline at end of file diff --git a/src/App.js b/src/App.js index 4a6f800f..3d5c8d16 100644 --- a/src/App.js +++ b/src/App.js @@ -1,17 +1,26 @@ import React from "react"; import logo from "./logo.png"; import "./App.css"; +import ImageTile from "./components/ImageTile"; +import ResponsiveAppBar from "./components/ResponsiveAppBar"; +import SearchBar from "./components/SearchBar"; class App extends React.Component { render() { return (
+ +
+ +
- logo -

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

+ +
+ +
+
+
); } diff --git a/src/components/AdminUpload.js b/src/components/AdminUpload.js new file mode 100644 index 00000000..e69de29b diff --git a/src/components/ImageTile.js b/src/components/ImageTile.js new file mode 100644 index 00000000..3e0e83c1 --- /dev/null +++ b/src/components/ImageTile.js @@ -0,0 +1,100 @@ +import React from 'react'; +import ImageList from '@mui/material/ImageList'; +import ImageListItem from '@mui/material/ImageListItem'; + + +export default function ImageTile() { + +function srcset(image, size, rows = 1, cols = 1) { + return { + src: `${image}?w=${size * cols}&h=${size * rows}&fit=crop&auto=format`, + srcSet: `${image}?w=${size * cols}&h=${ + size * rows + }&fit=crop&auto=format&dpr=2 2x`, + }; +} + +const itemData = [ + { + img: 'https://images.unsplash.com/photo-1551963831-b3b1ca40c98e', + title: 'Breakfast', + rows: 2, + cols: 2, + }, + { + img: 'https://images.unsplash.com/photo-1551782450-a2132b4ba21d', + title: 'Burger', + }, + { + img: 'https://images.unsplash.com/photo-1522770179533-24471fcdba45', + title: 'Camera', + }, + { + img: 'https://images.unsplash.com/photo-1444418776041-9c7e33cc5a9c', + title: 'Coffee', + cols: 2, + }, + { + img: 'https://images.unsplash.com/photo-1533827432537-70133748f5c8', + title: 'Hats', + cols: 2, + }, + { + img: 'https://images.unsplash.com/photo-1558642452-9d2a7deb7f62', + title: 'Honey', + author: '@arwinneil', + rows: 2, + cols: 2, + }, + { + img: 'https://images.unsplash.com/photo-1516802273409-68526ee1bdd6', + title: 'Basketball', + }, + { + img: 'https://images.unsplash.com/photo-1518756131217-31eb79b20e8f', + title: 'Fern', + }, + { + img: 'https://images.unsplash.com/photo-1597645587822-e99fa5d45d25', + title: 'Mushrooms', + rows: 2, + cols: 2, + }, + { + img: 'https://images.unsplash.com/photo-1567306301408-9b74779a11af', + title: 'Tomato basil', + }, + { + img: 'https://images.unsplash.com/photo-1471357674240-e1a485acb3e1', + title: 'Sea star', + }, + { + img: 'https://images.unsplash.com/photo-1589118949245-7d38baf380d6', + title: 'Bike', + cols: 2, + }, + ]; + + return ( +
+ + {itemData.map((item) => ( + + {item.title} + + ))} + +
+ ); +} + + diff --git a/src/components/ImgDownload.js b/src/components/ImgDownload.js new file mode 100644 index 00000000..e6431294 --- /dev/null +++ b/src/components/ImgDownload.js @@ -0,0 +1,38 @@ +import React from 'react'; +import JSZip from 'jszip'; +import { saveAs } from 'file-saver'; + + +const handleDownload = (imageUrls) => { + const zip = new JSZip(); + // const imageUrls = ['image1.jpg', 'image2.png', 'image3.gif']; // Replace with the URLs or file paths of your images + + const addImagesToZip = async () => { + const imagePromises = imageUrls.map((url) => { + return fetch(url) + .then((response) => response.blob()) + .then((blob) => { + const filename = url.substring(url.lastIndexOf('/') + 1); + zip.file(filename, blob); + }); + }); + + await Promise.all(imagePromises); + const zipBlob = await zip.generateAsync({ type: 'blob' }); + saveAs(zipBlob, 'pixfolio-images.zip'); + }; + + addImagesToZip(); + }; + + + const ImgDownload = () => { + return ( +
+ +
+ ); + }; + + export default ImgDownload; + \ No newline at end of file diff --git a/src/components/ResponsiveAppBar.js b/src/components/ResponsiveAppBar.js new file mode 100644 index 00000000..d352430d --- /dev/null +++ b/src/components/ResponsiveAppBar.js @@ -0,0 +1,167 @@ +import * as React from 'react'; +import AppBar from '@mui/material/AppBar'; +import Box from '@mui/material/Box'; +import Toolbar from '@mui/material/Toolbar'; +import IconButton from '@mui/material/IconButton'; +import Typography from '@mui/material/Typography'; +import Menu from '@mui/material/Menu'; +import MenuIcon from '@mui/icons-material/Menu'; +import Container from '@mui/material/Container'; +import Avatar from '@mui/material/Avatar'; +import Button from '@mui/material/Button'; +import Tooltip from '@mui/material/Tooltip'; +import MenuItem from '@mui/material/MenuItem'; +import AdbIcon from '@mui/icons-material/Adb'; + +const pages = ['About Us']; +const settings = ['Profile', 'Account', 'Dashboard', 'Logout']; + +function ResponsiveAppBar() { + const [anchorElNav, setAnchorElNav] = React.useState(null); + const [anchorElUser, setAnchorElUser] = React.useState(null); + + const handleOpenNavMenu = (event) => { + setAnchorElNav(event.currentTarget); + }; + const handleOpenUserMenu = (event) => { + setAnchorElUser(event.currentTarget); + }; + + const handleCloseNavMenu = () => { + setAnchorElNav(null); + }; + + const handleCloseUserMenu = () => { + setAnchorElUser(null); + }; + + return ( + + + + + + PIXFOLIO + + + + + + + + + {pages.map((page) => ( + + {page} + + ))} + + + + + + + LOGO + + + {pages.map((page) => ( + + ))} + + + + + + + + + + {settings.map((setting) => ( + + {setting} + + ))} + + + + + + ); +} +export default ResponsiveAppBar; + + diff --git a/src/components/SearchBar.js b/src/components/SearchBar.js new file mode 100644 index 00000000..9d1a89f7 --- /dev/null +++ b/src/components/SearchBar.js @@ -0,0 +1,31 @@ +import { Container, InputAdornment, TextField } from "@mui/material"; +import { useState } from "react"; +import SearchIcon from "@mui/icons-material/Search"; + +export default function SearchBar() { + const [searchTerm, setSearchTerm] = useState(""); + + const handleChange = (event) => { + setSearchTerm(event.target.value); + }; + + return ( + + + + + ), + }} + /> + + ); +} \ No newline at end of file From 55559d5cc7e45a8bcfbf4e4038d4f4adce404ffe Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Sat, 17 Jun 2023 19:04:23 +0800 Subject: [PATCH 02/35] added admin page,react router to app.js and dropzone --- package-lock.json | 120 +++++++++++++++++++++++++++++++++ package.json | 2 + src/App.css | 20 +++++- src/App.js | 31 +++++---- src/components/AdminUpload.js | 47 +++++++++++++ src/components/Home.js | 76 +++++++++++++++++++++ src/components/ImageTile.js | 121 ++++++++-------------------------- src/components/ImgDownload.js | 48 ++++++++------ 8 files changed, 334 insertions(+), 131 deletions(-) create mode 100644 src/components/Home.js diff --git a/package-lock.json b/package-lock.json index 338a04b3..a3b47465 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,8 @@ "jszip": "^3.10.1", "react": "^18.1.0", "react-dom": "^18.1.0", + "react-dropzone": "^14.2.3", + "react-router-dom": "^6.13.0", "react-scripts": "5.0.1" } }, @@ -3289,6 +3291,14 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@remix-run/router": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.3.tgz", + "integrity": "sha512-EXJysQ7J3veRECd0kZFQwYYd5sJMcq2O/m60zu1W2l3oVQ9xtub8jTOtYRE0+M2iomyG/W3Ps7+vp2kna0C27Q==", + "engines": { + "node": ">=14" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -4645,6 +4655,14 @@ "node": ">= 4.0.0" } }, + "node_modules/attr-accept": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", + "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==", + "engines": { + "node": ">=4" + } + }, "node_modules/autoprefixer": { "version": "10.4.7", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.7.tgz", @@ -7532,6 +7550,17 @@ "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" }, + "node_modules/file-selector": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz", + "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/filelist": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.3.tgz", @@ -13649,6 +13678,22 @@ "react": "^18.1.0" } }, + "node_modules/react-dropzone": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz", + "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==", + "dependencies": { + "attr-accept": "^2.2.2", + "file-selector": "^0.6.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "react": ">= 16.8 || 18.0.0" + } + }, "node_modules/react-error-overlay": { "version": "6.0.11", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", @@ -13667,6 +13712,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.13.0.tgz", + "integrity": "sha512-Si6KnfEnJw7gUQkNa70dlpI1bul46FuSxX5t5WwlUBxE25DAz2BjVkwaK8Y2s242bQrZPXCpmwLPtIO5pv4tXg==", + "dependencies": { + "@remix-run/router": "1.6.3" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.13.0.tgz", + "integrity": "sha512-6Nqoqd7fgwxxVGdbiMHTpDHCYPq62d7Wk1Of7B82vH7ZPwwsRaIa22zRZKPPg413R5REVNiyuQPKDG1bubcOFA==", + "dependencies": { + "@remix-run/router": "1.6.3", + "react-router": "6.13.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -18643,6 +18718,11 @@ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" }, + "@remix-run/router": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.3.tgz", + "integrity": "sha512-EXJysQ7J3veRECd0kZFQwYYd5sJMcq2O/m60zu1W2l3oVQ9xtub8jTOtYRE0+M2iomyG/W3Ps7+vp2kna0C27Q==" + }, "@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -19669,6 +19749,11 @@ "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" }, + "attr-accept": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", + "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==" + }, "autoprefixer": { "version": "10.4.7", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.7.tgz", @@ -21776,6 +21861,14 @@ "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" }, + "file-selector": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz", + "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==", + "requires": { + "tslib": "^2.4.0" + } + }, "filelist": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.3.tgz", @@ -26070,6 +26163,16 @@ "scheduler": "^0.22.0" } }, + "react-dropzone": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz", + "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==", + "requires": { + "attr-accept": "^2.2.2", + "file-selector": "^0.6.0", + "prop-types": "^15.8.1" + } + }, "react-error-overlay": { "version": "6.0.11", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", @@ -26085,6 +26188,23 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" }, + "react-router": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.13.0.tgz", + "integrity": "sha512-Si6KnfEnJw7gUQkNa70dlpI1bul46FuSxX5t5WwlUBxE25DAz2BjVkwaK8Y2s242bQrZPXCpmwLPtIO5pv4tXg==", + "requires": { + "@remix-run/router": "1.6.3" + } + }, + "react-router-dom": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.13.0.tgz", + "integrity": "sha512-6Nqoqd7fgwxxVGdbiMHTpDHCYPq62d7Wk1Of7B82vH7ZPwwsRaIa22zRZKPPg413R5REVNiyuQPKDG1bubcOFA==", + "requires": { + "@remix-run/router": "1.6.3", + "react-router": "6.13.0" + } + }, "react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", diff --git a/package.json b/package.json index 1b3aea7b..ef4d03ae 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,8 @@ "jszip": "^3.10.1", "react": "^18.1.0", "react-dom": "^18.1.0", + "react-dropzone": "^14.2.3", + "react-router-dom": "^6.13.0", "react-scripts": "5.0.1" }, "scripts": { diff --git a/src/App.css b/src/App.css index 1b586f4c..22a3ee3a 100644 --- a/src/App.css +++ b/src/App.css @@ -12,6 +12,22 @@ color: white; } -.Gallery-img{ +.Gallery-img { margin: 0 1%; -} \ No newline at end of file +} + +.dropzone { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + padding: 20px; + border-width: 2px; + border-radius: 2px; + border-color: #eeeeee; + border-style: dashed; + background-color: #fafafa; + color: #bdbdbd; + outline: none; + transition: border 0.24s ease-in-out; +} diff --git a/src/App.js b/src/App.js index 3d5c8d16..a58360d9 100644 --- a/src/App.js +++ b/src/App.js @@ -1,27 +1,26 @@ import React from "react"; -import logo from "./logo.png"; import "./App.css"; -import ImageTile from "./components/ImageTile"; import ResponsiveAppBar from "./components/ResponsiveAppBar"; -import SearchBar from "./components/SearchBar"; +import { BrowserRouter, Route, Routes } from "react-router-dom"; + +//routes +import AdminUpload from "./components/AdminUpload"; +import Home from "./components/Home"; class App extends React.Component { render() { return ( -
- -
- -
-
- -
- + +
+ +
+ + } /> + } /> + +
- -
- -
+ ); } } diff --git a/src/components/AdminUpload.js b/src/components/AdminUpload.js index e69de29b..e3ea6589 100644 --- a/src/components/AdminUpload.js +++ b/src/components/AdminUpload.js @@ -0,0 +1,47 @@ +import React from "react"; +import { useDropzone } from "react-dropzone"; + +function AdminUpload(props) { + const { acceptedFiles, fileRejections, getRootProps, getInputProps } = + useDropzone({ + accept: { + "image/jpeg": [], + "image/png": [], + }, + }); + + const acceptedFileItems = acceptedFiles.map((file) => ( +
  • + {file.path} - {file.size} bytes +
  • + )); + + const fileRejectionItems = fileRejections.map(({ file, errors }) => ( +
  • + {file.path} - {file.size} bytes +
      + {errors.map((e) => ( +
    • {e.message}
    • + ))} +
    +
  • + )); + + return ( +
    +
    + +

    Drag 'n' drop some files here, or click to select files

    + (Only *.jpeg and *.png images will be accepted) +
    + +
    + ); +} + +export default AdminUpload; diff --git a/src/components/Home.js b/src/components/Home.js new file mode 100644 index 00000000..582cc06f --- /dev/null +++ b/src/components/Home.js @@ -0,0 +1,76 @@ +import React from "react"; +import ImageTile from "./ImageTile"; +import SearchBar from "./SearchBar"; +import ImgDownload from "./ImgDownload"; + +class App extends React.Component { + render() { + //To be replaced by image data from server + const itemData = [ + { + img: "https://images.unsplash.com/photo-1551963831-b3b1ca40c98e", + title: "Breakfast", + }, + { + img: "https://images.unsplash.com/photo-1551782450-a2132b4ba21d", + title: "Burger", + }, + { + img: "https://images.unsplash.com/photo-1522770179533-24471fcdba45", + title: "Camera", + }, + { + img: "https://images.unsplash.com/photo-1444418776041-9c7e33cc5a9c", + title: "Coffee", + }, + { + img: "https://images.unsplash.com/photo-1533827432537-70133748f5c8", + title: "Hats", + }, + { + img: "https://images.unsplash.com/photo-1558642452-9d2a7deb7f62", + title: "Honey", + author: "@arwinneil", + }, + { + img: "https://images.unsplash.com/photo-1516802273409-68526ee1bdd6", + title: "Basketball", + }, + { + img: "https://images.unsplash.com/photo-1518756131217-31eb79b20e8f", + title: "Fern", + }, + { + img: "https://images.unsplash.com/photo-1597645587822-e99fa5d45d25", + title: "Mushrooms", + }, + { + img: "https://images.unsplash.com/photo-1567306301408-9b74779a11af", + title: "Tomato basil", + }, + { + img: "https://images.unsplash.com/photo-1471357674240-e1a485acb3e1", + title: "Sea star", + }, + { + img: "https://images.unsplash.com/photo-1589118949245-7d38baf380d6", + title: "Bike", + }, + ]; + + return ( +
    + + +
    + +
    + +
    +
    +
    + ); + } +} + +export default App; diff --git a/src/components/ImageTile.js b/src/components/ImageTile.js index 3e0e83c1..ca8950cc 100644 --- a/src/components/ImageTile.js +++ b/src/components/ImageTile.js @@ -1,100 +1,37 @@ -import React from 'react'; -import ImageList from '@mui/material/ImageList'; -import ImageListItem from '@mui/material/ImageListItem'; +import React from "react"; +import ImageList from "@mui/material/ImageList"; +import ImageListItem from "@mui/material/ImageListItem"; +export default function ImageTile(props) { + //Function: Takes in the image props and display them -export default function ImageTile() { - -function srcset(image, size, rows = 1, cols = 1) { - return { - src: `${image}?w=${size * cols}&h=${size * rows}&fit=crop&auto=format`, - srcSet: `${image}?w=${size * cols}&h=${ - size * rows - }&fit=crop&auto=format&dpr=2 2x`, - }; -} + //function to actually setup the sizes and image details for the tiling + function srcset(image, size, rows = 1, cols = 1) { + return { + src: `${image}?w=${size * cols}&h=${size * rows}&fit=crop&auto=format`, + srcSet: `${image}?w=${size * cols}&h=${ + size * rows + }&fit=crop&auto=format&dpr=2 2x`, + }; + } -const itemData = [ - { - img: 'https://images.unsplash.com/photo-1551963831-b3b1ca40c98e', - title: 'Breakfast', - rows: 2, - cols: 2, - }, - { - img: 'https://images.unsplash.com/photo-1551782450-a2132b4ba21d', - title: 'Burger', - }, - { - img: 'https://images.unsplash.com/photo-1522770179533-24471fcdba45', - title: 'Camera', - }, - { - img: 'https://images.unsplash.com/photo-1444418776041-9c7e33cc5a9c', - title: 'Coffee', - cols: 2, - }, - { - img: 'https://images.unsplash.com/photo-1533827432537-70133748f5c8', - title: 'Hats', - cols: 2, - }, - { - img: 'https://images.unsplash.com/photo-1558642452-9d2a7deb7f62', - title: 'Honey', - author: '@arwinneil', - rows: 2, - cols: 2, - }, - { - img: 'https://images.unsplash.com/photo-1516802273409-68526ee1bdd6', - title: 'Basketball', - }, - { - img: 'https://images.unsplash.com/photo-1518756131217-31eb79b20e8f', - title: 'Fern', - }, - { - img: 'https://images.unsplash.com/photo-1597645587822-e99fa5d45d25', - title: 'Mushrooms', - rows: 2, - cols: 2, - }, - { - img: 'https://images.unsplash.com/photo-1567306301408-9b74779a11af', - title: 'Tomato basil', - }, - { - img: 'https://images.unsplash.com/photo-1471357674240-e1a485acb3e1', - title: 'Sea star', - }, - { - img: 'https://images.unsplash.com/photo-1589118949245-7d38baf380d6', - title: 'Bike', - cols: 2, - }, - ]; - return (
    - - {itemData.map((item) => ( - - {item.title} - - ))} - + + {props.ImageObjects.map((item) => ( + + {item.title} + + ))} +
    ); } - - diff --git a/src/components/ImgDownload.js b/src/components/ImgDownload.js index e6431294..c33fcc46 100644 --- a/src/components/ImgDownload.js +++ b/src/components/ImgDownload.js @@ -1,38 +1,44 @@ -import React from 'react'; -import JSZip from 'jszip'; -import { saveAs } from 'file-saver'; +import React, { useState } from "react"; +import JSZip from "jszip"; +import { saveAs } from "file-saver"; +const ImgDownload = (props) => { + const [imgUrl, setimgUrl] = useState(null); -const handleDownload = (imageUrls) => { + const handleDownload = (imageUrls) => { const zip = new JSZip(); // const imageUrls = ['image1.jpg', 'image2.png', 'image3.gif']; // Replace with the URLs or file paths of your images - const addImagesToZip = async () => { - const imagePromises = imageUrls.map((url) => { + const imagePromises = imgUrl.map((url) => { return fetch(url) .then((response) => response.blob()) .then((blob) => { - const filename = url.substring(url.lastIndexOf('/') + 1); + const filename = url.substring(url.lastIndexOf("/") + 1); zip.file(filename, blob); }); }); - + await Promise.all(imagePromises); - const zipBlob = await zip.generateAsync({ type: 'blob' }); - saveAs(zipBlob, 'pixfolio-images.zip'); + const zipBlob = await zip.generateAsync({ type: "blob" }); + saveAs(zipBlob, "pixfolio-images.zip"); }; - + addImagesToZip(); }; - - const ImgDownload = () => { - return ( -
    - -
    - ); + const mapDownload = () => { + //map into an arrage of img links for download + const objectList = props.ImageObjects; + const imageUrls = objectList.map((imageObject) => imageObject.img); + setimgUrl(imageUrls); //set the state for downloads + handleDownload(imageUrls); }; - - export default ImgDownload; - \ No newline at end of file + + return ( +
    + +
    + ); +}; + +export default ImgDownload; From ab1a8bd228354e2f22b7b64b71c4a1b936854730 Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Sat, 17 Jun 2023 23:40:17 +0800 Subject: [PATCH 03/35] added admin page with features for password generation, imges file uploads preview etc. --- src/App.css | 49 ++++++++- src/components/AdminUpload.js | 188 +++++++++++++++++++++++++++------- 2 files changed, 200 insertions(+), 37 deletions(-) diff --git a/src/App.css b/src/App.css index 22a3ee3a..9962555a 100644 --- a/src/App.css +++ b/src/App.css @@ -17,7 +17,6 @@ } .dropzone { - flex: 1; display: flex; flex-direction: column; align-items: center; @@ -31,3 +30,51 @@ outline: none; transition: border 0.24s ease-in-out; } + +.dropzone-area { + width: 100%; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + min-height: 100px; +} +.upload-form { + max-width: 50%; + margin: 0 auto; +} + +.admin-form { + margin-top: 30px; +} + +.thumbsContainer { + display: flex; + flex-direction: row; + flex-wrap: wrap; + margin-top: 16px; +} + +.thumb { + display: inline-flex; + border-radius: 2px; + border: 1px solid #eaeaea; + margin-bottom: 8px; + margin-right: 8px; + width: 100px; + height: 100px; + padding: 4px; + box-sizing: border-box; +} + +.thumbInner { + display: flex; + min-width: 0; + overflow: hidden; +} + +.img { + display: block; + width: auto; + height: 100%; +} diff --git a/src/components/AdminUpload.js b/src/components/AdminUpload.js index e3ea6589..0e5c3d19 100644 --- a/src/components/AdminUpload.js +++ b/src/components/AdminUpload.js @@ -1,46 +1,162 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import { useDropzone } from "react-dropzone"; +import { + TextField, + FormLabel, + InputAdornment, + IconButton, + Button, +} from "@mui/material"; +import AutorenewIcon from "@mui/icons-material/Autorenew"; +import SendIcon from "@mui/icons-material/Send"; function AdminUpload(props) { - const { acceptedFiles, fileRejections, getRootProps, getInputProps } = - useDropzone({ - accept: { - "image/jpeg": [], - "image/png": [], - }, - }); - - const acceptedFileItems = acceptedFiles.map((file) => ( -
  • - {file.path} - {file.size} bytes -
  • - )); + const [name, setName] = useState(""); + const [email, setEmail] = useState(""); + const [pass, setPass] = useState(""); + const [files, setFiles] = useState([]); + + // 1. Function to set state on the files upon dropping + + // 2. Perform your form submission logic here, along with file upload handling + const handleSubmit = (event) => { + event.preventDefault(); + + // Perform your form submission logic here, along with file upload handling + // Access the selected files from the "files" state and include them in your form data + + console.log("Form submitted with files:", files); + }; + + const handleNameChange = (event) => { + setName(event.target.value); + }; + + const handleEmailChange = (event) => { + setEmail(event.target.value); + }; + + const handlePassChange = (event) => { + setPass(event.target.value); + }; - const fileRejectionItems = fileRejections.map(({ file, errors }) => ( -
  • - {file.path} - {file.size} bytes -
      - {errors.map((e) => ( -
    • {e.message}
    • - ))} -
    -
  • + //2b. Password Generator + function generateRandomPassword(length) { + const charset = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()"; + let password = ""; + + for (let i = 0; i < length; i++) { + const randomIndex = Math.floor(Math.random() * charset.length); + password += charset.charAt(randomIndex); + } + + return password; + } + + const genPass = () => { + // Usage example + const password = generateRandomPassword(8); + setPass(password); + }; + + // 3. Dropzone Params: Restricting Dropzone to only images files + const { getRootProps, getInputProps } = useDropzone({ + accept: { + "image/jpeg": [], + "image/png": [], + }, + onDrop: (acceptedFiles) => { + setFiles( + acceptedFiles.map((file) => + Object.assign(file, { + preview: URL.createObjectURL(file), + }) + ) + ); + }, + }); + + const thumbs = files.map((file) => ( +
    +
    + preview files { + URL.revokeObjectURL(file.preview); + }} + /> +
    +
    )); + useEffect(() => { + // Make sure to revoke the data uris to avoid memory leaks, will run on unmount + return () => files.forEach((file) => URL.revokeObjectURL(file.preview)); + }, []); + return ( -
    -
    - -

    Drag 'n' drop some files here, or click to select files

    - (Only *.jpeg and *.png images will be accepted) -
    - -
    +
    +
    + + {" "} + User Account Creation + + + + + + + + + ), + }} + /> +
    +
    + +

    + Drag and drop image files here, or click to select files +

    + + (Only *.jpeg and *.png images will be accepted) + +
    +
    + + + +
    ); } From dc8e8af0f07977c0fef649484b1dd7fb33012d31 Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Sun, 18 Jun 2023 02:34:27 +0800 Subject: [PATCH 04/35] added snackbar mui --- src/components/AdminUpload.js | 73 ++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/src/components/AdminUpload.js b/src/components/AdminUpload.js index 0e5c3d19..609acdc2 100644 --- a/src/components/AdminUpload.js +++ b/src/components/AdminUpload.js @@ -6,28 +6,71 @@ import { InputAdornment, IconButton, Button, + Snackbar, } from "@mui/material"; import AutorenewIcon from "@mui/icons-material/Autorenew"; import SendIcon from "@mui/icons-material/Send"; +import MuiAlert from "@mui/material/Alert"; function AdminUpload(props) { const [name, setName] = useState(""); const [email, setEmail] = useState(""); const [pass, setPass] = useState(""); const [files, setFiles] = useState([]); + const [openSuccessSnackbar, setOpenSuccessSnackbar] = useState(false); + const [openErrorSnackbar, setOpenErrorSnackbar] = useState(false); // 1. Function to set state on the files upon dropping // 2. Perform your form submission logic here, along with file upload handling + + const Alert = React.forwardRef(function Alert(props, ref) { + return ; + }); + + const handleCloseSuccessSnackbar = () => { + setOpenSuccessSnackbar(false); + }; + + const handleCloseErrorSnackbar = () => { + setOpenErrorSnackbar(false); + }; + const handleSubmit = (event) => { event.preventDefault(); - // Perform your form submission logic here, along with file upload handling // Access the selected files from the "files" state and include them in your form data + // Perform form validation before submission + if (isFormValid()) { + // Submit the form + setName(""); //Reset the form + setEmail(""); //Reset the form + setPass(""); //Reset the form + setFiles([]); //Reset the form + setOpenSuccessSnackbar(true); + setOpenErrorSnackbar(false); + console.log("Form submitted successfully"); + console.log("Form submitted with files:", files); + // Additional submission logic + } else { + setOpenErrorSnackbar(true); + setOpenSuccessSnackbar(false); + console.log("Form is not valid. Please fill in all fields."); + } + }; - console.log("Form submitted with files:", files); + // Validate form fields + const isFormValid = () => { + return ( + name.trim() !== "" && + email.trim() !== "" && + pass.trim() !== "" && + files.length !== 0 + ); }; + //handling value inputs and display + const handleNameChange = (event) => { setName(event.target.value); }; @@ -105,6 +148,32 @@ function AdminUpload(props) { {" "} User Account Creation + + + Form submitted successfully. + + + + + Please fill in all fields! + + Date: Sat, 17 Jun 2023 20:36:54 +0800 Subject: [PATCH 05/35] added firebase storage, ability to upload ImageObject database schema --- package-lock.json | 1439 ++++++++++++++++++++++++++++ package.json | 1 + public/index.html | 2 +- src/components/AdminUpload.js | 46 +- src/components/ResponsiveAppBar.js | 8 +- src/firebase.js | 30 + src/logo.png | Bin 29578 -> 0 bytes 7 files changed, 1520 insertions(+), 6 deletions(-) create mode 100644 src/firebase.js delete mode 100755 src/logo.png diff --git a/package-lock.json b/package-lock.json index a3b47465..2d107431 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@mui/icons-material": "^5.11.16", "@mui/material": "^5.13.5", "file-saver": "^2.0.5", + "firebase": "^9.22.2", "jszip": "^3.10.1", "react": "^18.1.0", "react-dom": "^18.1.0", @@ -2217,6 +2218,613 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@firebase/analytics": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.0.tgz", + "integrity": "sha512-Locv8gAqx0e+GX/0SI3dzmBY5e9kjVDtD+3zCFLJ0tH2hJwuCAiL+5WkHuxKj92rqQj/rvkBUCfA1ewlX2hehg==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/analytics-compat": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.6.tgz", + "integrity": "sha512-4MqpVLFkGK7NJf/5wPEEP7ePBJatwYpyjgJ+wQHQGHfzaCDgntOnl9rL2vbVGGKCnRqWtZDIWhctB86UWXaX2Q==", + "dependencies": { + "@firebase/analytics": "0.10.0", + "@firebase/analytics-types": "0.8.0", + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/analytics-types": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.0.tgz", + "integrity": "sha512-iRP+QKI2+oz3UAh4nPEq14CsEjrjD6a5+fuypjScisAh9kXKFvdJOZJDwk7kikLvWVLGEs9+kIUS4LPQV7VZVw==" + }, + "node_modules/@firebase/app": { + "version": "0.9.12", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.9.12.tgz", + "integrity": "sha512-VsE/WHZU8M9BCnHMbOi3FqIVIsoG4FlEehjp+XCDNE0zxn4BGgnpLdBu6/r9Bg565b1ND7dm6LSVRtewmeRb3w==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "idb": "7.1.1", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-check": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.8.0.tgz", + "integrity": "sha512-dRDnhkcaC2FspMiRK/Vbp+PfsOAEP6ZElGm9iGFJ9fDqHoPs0HOPn7dwpJ51lCFi1+2/7n5pRPGhqF/F03I97g==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/app-check-compat": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.7.tgz", + "integrity": "sha512-cW682AxsyP1G+Z0/P7pO/WT2CzYlNxoNe5QejVarW2o5ZxeWSSPAiVEwpEpQR/bUlUmdeWThYTMvBWaopdBsqw==", + "dependencies": { + "@firebase/app-check": "0.8.0", + "@firebase/app-check-types": "0.5.0", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/app-check-interop-types": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.0.tgz", + "integrity": "sha512-xAxHPZPIgFXnI+vb4sbBjZcde7ZluzPPaSK7Lx3/nmuVk4TjZvnL8ONnkd4ERQKL8WePQySU+pRcWkh8rDf5Sg==" + }, + "node_modules/@firebase/app-check-types": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.0.tgz", + "integrity": "sha512-uwSUj32Mlubybw7tedRzR24RP8M8JUVR3NPiMk3/Z4bCmgEKTlQBwMXrehDAZ2wF+TsBq0SN1c6ema71U/JPyQ==" + }, + "node_modules/@firebase/app-compat": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.12.tgz", + "integrity": "sha512-3EfputoACcXvgi2uN9RUQVDYSmRSR4R4TWJW9Wvs4hTib2I26ldvVhDHaheQq90IwGYrRa+TTWuzr4a5dCRkVQ==", + "dependencies": { + "@firebase/app": "0.9.12", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-types": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.0.tgz", + "integrity": "sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q==" + }, + "node_modules/@firebase/app/node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==" + }, + "node_modules/@firebase/auth": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.23.2.tgz", + "integrity": "sha512-dM9iJ0R6tI1JczuGSxXmQbXAgtYie0K4WvKcuyuSTCu9V8eEDiz4tfa1sO3txsfvwg7nOY3AjoCyMYEdqZ8hdg==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/auth-compat": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.4.2.tgz", + "integrity": "sha512-Q30e77DWXFmXEt5dg5JbqEDpjw9y3/PcP9LslDPR7fARmAOTIY9MM6HXzm9KC+dlrKH/+p6l8g9ifJiam9mc4A==", + "dependencies": { + "@firebase/auth": "0.23.2", + "@firebase/auth-types": "0.12.0", + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.1.tgz", + "integrity": "sha512-VOaGzKp65MY6P5FI84TfYKBXEPi6LmOCSMMzys6o2BN2LOsqy7pCuZCup7NYnfbk5OkkQKzvIfHOzTm0UDpkyg==" + }, + "node_modules/@firebase/auth-types": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.12.0.tgz", + "integrity": "sha512-pPwaZt+SPOshK8xNoiQlK5XIrS97kFYc3Rc7xmy373QsOJ9MmqXxLaYssP5Kcds4wd2qK//amx/c+A8O2fVeZA==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/component": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.4.tgz", + "integrity": "sha512-rLMyrXuO9jcAUCaQXCMjCMUsWrba5fzHlNK24xz5j2W6A/SRmK8mZJ/hn7V0fViLbxC0lPMtrK1eYzk6Fg03jA==", + "dependencies": { + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.14.4.tgz", + "integrity": "sha512-+Ea/IKGwh42jwdjCyzTmeZeLM3oy1h0mFPsTy6OqCWzcu/KFqRAr5Tt1HRCOBlNOdbh84JPZC47WLU18n2VbxQ==", + "dependencies": { + "@firebase/auth-interop-types": "0.2.1", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.3.4.tgz", + "integrity": "sha512-kuAW+l+sLMUKBThnvxvUZ+Q1ZrF/vFJ58iUY9kAcbX48U03nVzIF6Tmkf0p3WVQwMqiXguSgtOPIB6ZCeF+5Gg==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/database": "0.14.4", + "@firebase/database-types": "0.10.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.10.4.tgz", + "integrity": "sha512-dPySn0vJ/89ZeBac70T+2tWWPiJXWbmRygYv0smT5TfE3hDrQ09eKMF3Y+vMlTdrMWq7mUdYW5REWPSGH4kAZQ==", + "dependencies": { + "@firebase/app-types": "0.9.0", + "@firebase/util": "1.9.3" + } + }, + "node_modules/@firebase/firestore": { + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-3.12.2.tgz", + "integrity": "sha512-6EDIJ2V4hlUkPvAb1uH5DAz65ZvhStIM1oYGSUx6mt2UdEDu/0CAVS7xYBY6niTyM/+2r6XBW3hYG/1x1V27vg==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "@firebase/webchannel-wrapper": "0.10.1", + "@grpc/grpc-js": "~1.7.0", + "@grpc/proto-loader": "^0.6.13", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=10.10.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/firestore-compat": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.11.tgz", + "integrity": "sha512-jPhySBBp6+Vt750WmeCK4it/NV9YHQEX+jJ7Va8wHOhVejy0zUhL5TsLF6Bz3hCjb4Dxn6XVgvuSqiuqY16yWw==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/firestore": "3.12.2", + "@firebase/firestore-types": "2.5.1", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/firestore-types": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-2.5.1.tgz", + "integrity": "sha512-xG0CA6EMfYo8YeUxC8FeDzf6W3FX1cLlcAGBYV6Cku12sZRI81oWcu61RSKM66K6kUENP+78Qm8mvroBcm1whw==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/functions": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.10.0.tgz", + "integrity": "sha512-2U+fMNxTYhtwSpkkR6WbBcuNMOVaI7MaH3cZ6UAeNfj7AgEwHwMIFLPpC13YNZhno219F0lfxzTAA0N62ndWzA==", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.0", + "@firebase/auth-interop-types": "0.2.1", + "@firebase/component": "0.6.4", + "@firebase/messaging-interop-types": "0.2.0", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/functions-compat": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.5.tgz", + "integrity": "sha512-uD4jwgwVqdWf6uc3NRKF8cSZ0JwGqSlyhPgackyUPe+GAtnERpS4+Vr66g0b3Gge0ezG4iyHo/EXW/Hjx7QhHw==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/functions": "0.10.0", + "@firebase/functions-types": "0.6.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/functions-types": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.0.tgz", + "integrity": "sha512-hfEw5VJtgWXIRf92ImLkgENqpL6IWpYaXVYiRkFY1jJ9+6tIhWM7IzzwbevwIIud/jaxKVdRzD7QBWfPmkwCYw==" + }, + "node_modules/@firebase/installations": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.4.tgz", + "integrity": "sha512-u5y88rtsp7NYkCHC3ElbFBrPtieUybZluXyzl7+4BsIz4sqb4vSAuwHEUgCgCeaQhvsnxDEU6icly8U9zsJigA==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "idb": "7.0.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/installations-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.4.tgz", + "integrity": "sha512-LI9dYjp0aT9Njkn9U4JRrDqQ6KXeAmFbRC0E7jI7+hxl5YmRWysq5qgQl22hcWpTk+cm3es66d/apoDU/A9n6Q==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/installations-types": "0.5.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/installations-types": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.0.tgz", + "integrity": "sha512-9DP+RGfzoI2jH7gY4SlzqvZ+hr7gYzPODrbzVD82Y12kScZ6ZpRg/i3j6rleto8vTFC8n6Len4560FnV1w2IRg==", + "peerDependencies": { + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/installations/node_modules/idb": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", + "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" + }, + "node_modules/@firebase/logger": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.0.tgz", + "integrity": "sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/messaging": { + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.4.tgz", + "integrity": "sha512-6JLZct6zUaex4g7HI3QbzeUrg9xcnmDAPTWpkoMpd/GoSVWH98zDoWXMGrcvHeCAIsLpFMe4MPoZkJbrPhaASw==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/messaging-interop-types": "0.2.0", + "@firebase/util": "1.9.3", + "idb": "7.0.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/messaging-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.4.tgz", + "integrity": "sha512-lyFjeUhIsPRYDPNIkYX1LcZMpoVbBWXX4rPl7c/rqc7G+EUea7IEtSt4MxTvh6fDfPuzLn7+FZADfscC+tNMfg==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/messaging": "0.12.4", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/messaging-interop-types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.0.tgz", + "integrity": "sha512-ujA8dcRuVeBixGR9CtegfpU4YmZf3Lt7QYkcj693FFannwNuZgfAYaTmbJ40dtjB81SAu6tbFPL9YLNT15KmOQ==" + }, + "node_modules/@firebase/messaging/node_modules/idb": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", + "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" + }, + "node_modules/@firebase/performance": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.6.4.tgz", + "integrity": "sha512-HfTn/bd8mfy/61vEqaBelNiNnvAbUtME2S25A67Nb34zVuCSCRIX4SseXY6zBnOFj3oLisaEqhVcJmVPAej67g==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/performance-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.4.tgz", + "integrity": "sha512-nnHUb8uP9G8islzcld/k6Bg5RhX62VpbAb/Anj7IXs/hp32Eb2LqFPZK4sy3pKkBUO5wcrlRWQa6wKOxqlUqsg==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/performance": "0.6.4", + "@firebase/performance-types": "0.2.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/performance-types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.0.tgz", + "integrity": "sha512-kYrbr8e/CYr1KLrLYZZt2noNnf+pRwDq2KK9Au9jHrBMnb0/C9X9yWSXmZkFt4UIdsQknBq8uBB7fsybZdOBTA==" + }, + "node_modules/@firebase/remote-config": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.4.tgz", + "integrity": "sha512-x1ioTHGX8ZwDSTOVp8PBLv2/wfwKzb4pxi0gFezS5GCJwbLlloUH4YYZHHS83IPxnua8b6l0IXUaWd0RgbWwzQ==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/remote-config-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.4.tgz", + "integrity": "sha512-FKiki53jZirrDFkBHglB3C07j5wBpitAaj8kLME6g8Mx+aq7u9P7qfmuSRytiOItADhWUj7O1JIv7n9q87SuwA==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/remote-config": "0.4.4", + "@firebase/remote-config-types": "0.3.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/remote-config-types": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.3.0.tgz", + "integrity": "sha512-RtEH4vdcbXZuZWRZbIRmQVBNsE7VDQpet2qFvq6vwKLBIQRQR5Kh58M4ok3A3US8Sr3rubYnaGqZSurCwI8uMA==" + }, + "node_modules/@firebase/storage": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.11.2.tgz", + "integrity": "sha512-CtvoFaBI4hGXlXbaCHf8humajkbXhs39Nbh6MbNxtwJiCqxPy9iH3D3CCfXAvP0QvAAwmJUTK3+z9a++Kc4nkA==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/storage-compat": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.2.tgz", + "integrity": "sha512-wvsXlLa9DVOMQJckbDNhXKKxRNNewyUhhbXev3t8kSgoCotd1v3MmqhKKz93ePhDnhHnDs7bYHy+Qa8dRY6BXw==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/storage": "0.11.2", + "@firebase/storage-types": "0.8.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/storage-types": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.0.tgz", + "integrity": "sha512-isRHcGrTs9kITJC0AVehHfpraWFui39MPaU7Eo8QfWlqW7YPymBmRgjDrlOgFdURh6Cdeg07zmkLP5tzTKRSpg==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/util": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.3.tgz", + "integrity": "sha512-DY02CRhOZwpzO36fHpuVysz6JZrscPiBXD0fXp6qSrL9oNOx5KWICKdR95C0lSITzxp0TZosVyHqzatE8JbcjA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/webchannel-wrapper": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.10.1.tgz", + "integrity": "sha512-Dq5rYfEpdeel0bLVN+nfD1VWmzCkK+pJbSjIawGE+RY4+NIJqhbUDDQjvV0NUK84fMfwxvtFoCtEe70HfZjFcw==" + }, + "node_modules/@grpc/grpc-js": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz", + "integrity": "sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==", + "dependencies": { + "@grpc/proto-loader": "^0.7.0", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.7.tgz", + "integrity": "sha512-1TIeXOi8TuSCQprPItwoMymZXxWT0CPxUhkrkeCUH+D8U7QDwQ6b7SUz2MaLuWM2llT+J/TVFLmQI5KtML3BhQ==", + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^7.0.0", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/grpc-js/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@grpc/grpc-js/node_modules/protobufjs": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.3.tgz", + "integrity": "sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@grpc/grpc-js/node_modules/protobufjs/node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "node_modules/@grpc/grpc-js/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@grpc/grpc-js/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", + "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.11.3", + "yargs": "^16.2.0" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.9.5", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", @@ -3291,6 +3899,60 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, "node_modules/@remix-run/router": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.3.tgz", @@ -3800,6 +4462,11 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=" }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, "node_modules/@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -7673,6 +8340,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/firebase": { + "version": "9.22.2", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-9.22.2.tgz", + "integrity": "sha512-eBXsaTzXPx3Y0QhuuluG/qR58tlOx2X/W0GKNoF004FcG9L2gHuvGu5/bIczvrPyfNOCqDF+I5I/kOQi8l9m0A==", + "dependencies": { + "@firebase/analytics": "0.10.0", + "@firebase/analytics-compat": "0.2.6", + "@firebase/app": "0.9.12", + "@firebase/app-check": "0.8.0", + "@firebase/app-check-compat": "0.3.7", + "@firebase/app-compat": "0.2.12", + "@firebase/app-types": "0.9.0", + "@firebase/auth": "0.23.2", + "@firebase/auth-compat": "0.4.2", + "@firebase/database": "0.14.4", + "@firebase/database-compat": "0.3.4", + "@firebase/firestore": "3.12.2", + "@firebase/firestore-compat": "0.3.11", + "@firebase/functions": "0.10.0", + "@firebase/functions-compat": "0.3.5", + "@firebase/installations": "0.6.4", + "@firebase/installations-compat": "0.2.4", + "@firebase/messaging": "0.12.4", + "@firebase/messaging-compat": "0.2.4", + "@firebase/performance": "0.6.4", + "@firebase/performance-compat": "0.2.4", + "@firebase/remote-config": "0.4.4", + "@firebase/remote-config-compat": "0.2.4", + "@firebase/storage": "0.11.2", + "@firebase/storage-compat": "0.3.2", + "@firebase/util": "1.9.3" + } + }, "node_modules/flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -11260,6 +11960,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -11285,6 +11990,11 @@ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -11607,6 +12317,44 @@ "tslib": "^2.0.3" } }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -13379,6 +14127,31 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -18012,6 +18785,521 @@ } } }, + "@firebase/analytics": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.0.tgz", + "integrity": "sha512-Locv8gAqx0e+GX/0SI3dzmBY5e9kjVDtD+3zCFLJ0tH2hJwuCAiL+5WkHuxKj92rqQj/rvkBUCfA1ewlX2hehg==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "@firebase/analytics-compat": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.6.tgz", + "integrity": "sha512-4MqpVLFkGK7NJf/5wPEEP7ePBJatwYpyjgJ+wQHQGHfzaCDgntOnl9rL2vbVGGKCnRqWtZDIWhctB86UWXaX2Q==", + "requires": { + "@firebase/analytics": "0.10.0", + "@firebase/analytics-types": "0.8.0", + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "@firebase/analytics-types": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.0.tgz", + "integrity": "sha512-iRP+QKI2+oz3UAh4nPEq14CsEjrjD6a5+fuypjScisAh9kXKFvdJOZJDwk7kikLvWVLGEs9+kIUS4LPQV7VZVw==" + }, + "@firebase/app": { + "version": "0.9.12", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.9.12.tgz", + "integrity": "sha512-VsE/WHZU8M9BCnHMbOi3FqIVIsoG4FlEehjp+XCDNE0zxn4BGgnpLdBu6/r9Bg565b1ND7dm6LSVRtewmeRb3w==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "dependencies": { + "idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==" + } + } + }, + "@firebase/app-check": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.8.0.tgz", + "integrity": "sha512-dRDnhkcaC2FspMiRK/Vbp+PfsOAEP6ZElGm9iGFJ9fDqHoPs0HOPn7dwpJ51lCFi1+2/7n5pRPGhqF/F03I97g==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "@firebase/app-check-compat": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.7.tgz", + "integrity": "sha512-cW682AxsyP1G+Z0/P7pO/WT2CzYlNxoNe5QejVarW2o5ZxeWSSPAiVEwpEpQR/bUlUmdeWThYTMvBWaopdBsqw==", + "requires": { + "@firebase/app-check": "0.8.0", + "@firebase/app-check-types": "0.5.0", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "@firebase/app-check-interop-types": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.0.tgz", + "integrity": "sha512-xAxHPZPIgFXnI+vb4sbBjZcde7ZluzPPaSK7Lx3/nmuVk4TjZvnL8ONnkd4ERQKL8WePQySU+pRcWkh8rDf5Sg==" + }, + "@firebase/app-check-types": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.0.tgz", + "integrity": "sha512-uwSUj32Mlubybw7tedRzR24RP8M8JUVR3NPiMk3/Z4bCmgEKTlQBwMXrehDAZ2wF+TsBq0SN1c6ema71U/JPyQ==" + }, + "@firebase/app-compat": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.12.tgz", + "integrity": "sha512-3EfputoACcXvgi2uN9RUQVDYSmRSR4R4TWJW9Wvs4hTib2I26ldvVhDHaheQq90IwGYrRa+TTWuzr4a5dCRkVQ==", + "requires": { + "@firebase/app": "0.9.12", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "@firebase/app-types": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.0.tgz", + "integrity": "sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q==" + }, + "@firebase/auth": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.23.2.tgz", + "integrity": "sha512-dM9iJ0R6tI1JczuGSxXmQbXAgtYie0K4WvKcuyuSTCu9V8eEDiz4tfa1sO3txsfvwg7nOY3AjoCyMYEdqZ8hdg==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + } + }, + "@firebase/auth-compat": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.4.2.tgz", + "integrity": "sha512-Q30e77DWXFmXEt5dg5JbqEDpjw9y3/PcP9LslDPR7fARmAOTIY9MM6HXzm9KC+dlrKH/+p6l8g9ifJiam9mc4A==", + "requires": { + "@firebase/auth": "0.23.2", + "@firebase/auth-types": "0.12.0", + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + } + }, + "@firebase/auth-interop-types": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.1.tgz", + "integrity": "sha512-VOaGzKp65MY6P5FI84TfYKBXEPi6LmOCSMMzys6o2BN2LOsqy7pCuZCup7NYnfbk5OkkQKzvIfHOzTm0UDpkyg==" + }, + "@firebase/auth-types": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.12.0.tgz", + "integrity": "sha512-pPwaZt+SPOshK8xNoiQlK5XIrS97kFYc3Rc7xmy373QsOJ9MmqXxLaYssP5Kcds4wd2qK//amx/c+A8O2fVeZA==", + "requires": {} + }, + "@firebase/component": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.4.tgz", + "integrity": "sha512-rLMyrXuO9jcAUCaQXCMjCMUsWrba5fzHlNK24xz5j2W6A/SRmK8mZJ/hn7V0fViLbxC0lPMtrK1eYzk6Fg03jA==", + "requires": { + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "@firebase/database": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.14.4.tgz", + "integrity": "sha512-+Ea/IKGwh42jwdjCyzTmeZeLM3oy1h0mFPsTy6OqCWzcu/KFqRAr5Tt1HRCOBlNOdbh84JPZC47WLU18n2VbxQ==", + "requires": { + "@firebase/auth-interop-types": "0.2.1", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "@firebase/database-compat": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.3.4.tgz", + "integrity": "sha512-kuAW+l+sLMUKBThnvxvUZ+Q1ZrF/vFJ58iUY9kAcbX48U03nVzIF6Tmkf0p3WVQwMqiXguSgtOPIB6ZCeF+5Gg==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/database": "0.14.4", + "@firebase/database-types": "0.10.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "@firebase/database-types": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.10.4.tgz", + "integrity": "sha512-dPySn0vJ/89ZeBac70T+2tWWPiJXWbmRygYv0smT5TfE3hDrQ09eKMF3Y+vMlTdrMWq7mUdYW5REWPSGH4kAZQ==", + "requires": { + "@firebase/app-types": "0.9.0", + "@firebase/util": "1.9.3" + } + }, + "@firebase/firestore": { + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-3.12.2.tgz", + "integrity": "sha512-6EDIJ2V4hlUkPvAb1uH5DAz65ZvhStIM1oYGSUx6mt2UdEDu/0CAVS7xYBY6niTyM/+2r6XBW3hYG/1x1V27vg==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "@firebase/webchannel-wrapper": "0.10.1", + "@grpc/grpc-js": "~1.7.0", + "@grpc/proto-loader": "^0.6.13", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + } + }, + "@firebase/firestore-compat": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.11.tgz", + "integrity": "sha512-jPhySBBp6+Vt750WmeCK4it/NV9YHQEX+jJ7Va8wHOhVejy0zUhL5TsLF6Bz3hCjb4Dxn6XVgvuSqiuqY16yWw==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/firestore": "3.12.2", + "@firebase/firestore-types": "2.5.1", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "@firebase/firestore-types": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-2.5.1.tgz", + "integrity": "sha512-xG0CA6EMfYo8YeUxC8FeDzf6W3FX1cLlcAGBYV6Cku12sZRI81oWcu61RSKM66K6kUENP+78Qm8mvroBcm1whw==", + "requires": {} + }, + "@firebase/functions": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.10.0.tgz", + "integrity": "sha512-2U+fMNxTYhtwSpkkR6WbBcuNMOVaI7MaH3cZ6UAeNfj7AgEwHwMIFLPpC13YNZhno219F0lfxzTAA0N62ndWzA==", + "requires": { + "@firebase/app-check-interop-types": "0.3.0", + "@firebase/auth-interop-types": "0.2.1", + "@firebase/component": "0.6.4", + "@firebase/messaging-interop-types": "0.2.0", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + } + }, + "@firebase/functions-compat": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.5.tgz", + "integrity": "sha512-uD4jwgwVqdWf6uc3NRKF8cSZ0JwGqSlyhPgackyUPe+GAtnERpS4+Vr66g0b3Gge0ezG4iyHo/EXW/Hjx7QhHw==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/functions": "0.10.0", + "@firebase/functions-types": "0.6.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "@firebase/functions-types": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.0.tgz", + "integrity": "sha512-hfEw5VJtgWXIRf92ImLkgENqpL6IWpYaXVYiRkFY1jJ9+6tIhWM7IzzwbevwIIud/jaxKVdRzD7QBWfPmkwCYw==" + }, + "@firebase/installations": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.4.tgz", + "integrity": "sha512-u5y88rtsp7NYkCHC3ElbFBrPtieUybZluXyzl7+4BsIz4sqb4vSAuwHEUgCgCeaQhvsnxDEU6icly8U9zsJigA==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "idb": "7.0.1", + "tslib": "^2.1.0" + }, + "dependencies": { + "idb": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", + "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" + } + } + }, + "@firebase/installations-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.4.tgz", + "integrity": "sha512-LI9dYjp0aT9Njkn9U4JRrDqQ6KXeAmFbRC0E7jI7+hxl5YmRWysq5qgQl22hcWpTk+cm3es66d/apoDU/A9n6Q==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/installations-types": "0.5.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "@firebase/installations-types": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.0.tgz", + "integrity": "sha512-9DP+RGfzoI2jH7gY4SlzqvZ+hr7gYzPODrbzVD82Y12kScZ6ZpRg/i3j6rleto8vTFC8n6Len4560FnV1w2IRg==", + "requires": {} + }, + "@firebase/logger": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.0.tgz", + "integrity": "sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==", + "requires": { + "tslib": "^2.1.0" + } + }, + "@firebase/messaging": { + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.4.tgz", + "integrity": "sha512-6JLZct6zUaex4g7HI3QbzeUrg9xcnmDAPTWpkoMpd/GoSVWH98zDoWXMGrcvHeCAIsLpFMe4MPoZkJbrPhaASw==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/messaging-interop-types": "0.2.0", + "@firebase/util": "1.9.3", + "idb": "7.0.1", + "tslib": "^2.1.0" + }, + "dependencies": { + "idb": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", + "integrity": "sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==" + } + } + }, + "@firebase/messaging-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.4.tgz", + "integrity": "sha512-lyFjeUhIsPRYDPNIkYX1LcZMpoVbBWXX4rPl7c/rqc7G+EUea7IEtSt4MxTvh6fDfPuzLn7+FZADfscC+tNMfg==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/messaging": "0.12.4", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "@firebase/messaging-interop-types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.0.tgz", + "integrity": "sha512-ujA8dcRuVeBixGR9CtegfpU4YmZf3Lt7QYkcj693FFannwNuZgfAYaTmbJ40dtjB81SAu6tbFPL9YLNT15KmOQ==" + }, + "@firebase/performance": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.6.4.tgz", + "integrity": "sha512-HfTn/bd8mfy/61vEqaBelNiNnvAbUtME2S25A67Nb34zVuCSCRIX4SseXY6zBnOFj3oLisaEqhVcJmVPAej67g==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "@firebase/performance-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.4.tgz", + "integrity": "sha512-nnHUb8uP9G8islzcld/k6Bg5RhX62VpbAb/Anj7IXs/hp32Eb2LqFPZK4sy3pKkBUO5wcrlRWQa6wKOxqlUqsg==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/performance": "0.6.4", + "@firebase/performance-types": "0.2.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "@firebase/performance-types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.0.tgz", + "integrity": "sha512-kYrbr8e/CYr1KLrLYZZt2noNnf+pRwDq2KK9Au9jHrBMnb0/C9X9yWSXmZkFt4UIdsQknBq8uBB7fsybZdOBTA==" + }, + "@firebase/remote-config": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.4.tgz", + "integrity": "sha512-x1ioTHGX8ZwDSTOVp8PBLv2/wfwKzb4pxi0gFezS5GCJwbLlloUH4YYZHHS83IPxnua8b6l0IXUaWd0RgbWwzQ==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/installations": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "@firebase/remote-config-compat": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.4.tgz", + "integrity": "sha512-FKiki53jZirrDFkBHglB3C07j5wBpitAaj8kLME6g8Mx+aq7u9P7qfmuSRytiOItADhWUj7O1JIv7n9q87SuwA==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/remote-config": "0.4.4", + "@firebase/remote-config-types": "0.3.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "@firebase/remote-config-types": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.3.0.tgz", + "integrity": "sha512-RtEH4vdcbXZuZWRZbIRmQVBNsE7VDQpet2qFvq6vwKLBIQRQR5Kh58M4ok3A3US8Sr3rubYnaGqZSurCwI8uMA==" + }, + "@firebase/storage": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.11.2.tgz", + "integrity": "sha512-CtvoFaBI4hGXlXbaCHf8humajkbXhs39Nbh6MbNxtwJiCqxPy9iH3D3CCfXAvP0QvAAwmJUTK3+z9a++Kc4nkA==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/util": "1.9.3", + "node-fetch": "2.6.7", + "tslib": "^2.1.0" + } + }, + "@firebase/storage-compat": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.2.tgz", + "integrity": "sha512-wvsXlLa9DVOMQJckbDNhXKKxRNNewyUhhbXev3t8kSgoCotd1v3MmqhKKz93ePhDnhHnDs7bYHy+Qa8dRY6BXw==", + "requires": { + "@firebase/component": "0.6.4", + "@firebase/storage": "0.11.2", + "@firebase/storage-types": "0.8.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "@firebase/storage-types": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.0.tgz", + "integrity": "sha512-isRHcGrTs9kITJC0AVehHfpraWFui39MPaU7Eo8QfWlqW7YPymBmRgjDrlOgFdURh6Cdeg07zmkLP5tzTKRSpg==", + "requires": {} + }, + "@firebase/util": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.3.tgz", + "integrity": "sha512-DY02CRhOZwpzO36fHpuVysz6JZrscPiBXD0fXp6qSrL9oNOx5KWICKdR95C0lSITzxp0TZosVyHqzatE8JbcjA==", + "requires": { + "tslib": "^2.1.0" + } + }, + "@firebase/webchannel-wrapper": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.10.1.tgz", + "integrity": "sha512-Dq5rYfEpdeel0bLVN+nfD1VWmzCkK+pJbSjIawGE+RY4+NIJqhbUDDQjvV0NUK84fMfwxvtFoCtEe70HfZjFcw==" + }, + "@grpc/grpc-js": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.3.tgz", + "integrity": "sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==", + "requires": { + "@grpc/proto-loader": "^0.7.0", + "@types/node": ">=12.12.47" + }, + "dependencies": { + "@grpc/proto-loader": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.7.tgz", + "integrity": "sha512-1TIeXOi8TuSCQprPItwoMymZXxWT0CPxUhkrkeCUH+D8U7QDwQ6b7SUz2MaLuWM2llT+J/TVFLmQI5KtML3BhQ==", + "requires": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^7.0.0", + "yargs": "^17.7.2" + } + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "protobufjs": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.3.tgz", + "integrity": "sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "dependencies": { + "long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + } + } + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + } + } + }, + "@grpc/proto-loader": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.13.tgz", + "integrity": "sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==", + "requires": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.11.3", + "yargs": "^16.2.0" + } + }, "@humanwhocodes/config-array": { "version": "0.9.5", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", @@ -18718,6 +20006,60 @@ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, "@remix-run/router": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.3.tgz", @@ -19094,6 +20436,11 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=" }, + "@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, "@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -21961,6 +23308,39 @@ "path-exists": "^4.0.0" } }, + "firebase": { + "version": "9.22.2", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-9.22.2.tgz", + "integrity": "sha512-eBXsaTzXPx3Y0QhuuluG/qR58tlOx2X/W0GKNoF004FcG9L2gHuvGu5/bIczvrPyfNOCqDF+I5I/kOQi8l9m0A==", + "requires": { + "@firebase/analytics": "0.10.0", + "@firebase/analytics-compat": "0.2.6", + "@firebase/app": "0.9.12", + "@firebase/app-check": "0.8.0", + "@firebase/app-check-compat": "0.3.7", + "@firebase/app-compat": "0.2.12", + "@firebase/app-types": "0.9.0", + "@firebase/auth": "0.23.2", + "@firebase/auth-compat": "0.4.2", + "@firebase/database": "0.14.4", + "@firebase/database-compat": "0.3.4", + "@firebase/firestore": "3.12.2", + "@firebase/firestore-compat": "0.3.11", + "@firebase/functions": "0.10.0", + "@firebase/functions-compat": "0.3.5", + "@firebase/installations": "0.6.4", + "@firebase/installations-compat": "0.2.4", + "@firebase/messaging": "0.12.4", + "@firebase/messaging-compat": "0.2.4", + "@firebase/performance": "0.6.4", + "@firebase/performance-compat": "0.2.4", + "@firebase/remote-config": "0.4.4", + "@firebase/remote-config-compat": "0.2.4", + "@firebase/storage": "0.11.2", + "@firebase/storage-compat": "0.3.2", + "@firebase/util": "1.9.3" + } + }, "flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -24548,6 +25928,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -24573,6 +25958,11 @@ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -24812,6 +26202,35 @@ "tslib": "^2.0.3" } }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } + }, "node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -25951,6 +27370,26 @@ } } }, + "protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + } + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", diff --git a/package.json b/package.json index ef4d03ae..f10cbbf8 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "@mui/icons-material": "^5.11.16", "@mui/material": "^5.13.5", "file-saver": "^2.0.5", + "firebase": "^9.22.2", "jszip": "^3.10.1", "react": "^18.1.0", "react-dom": "^18.1.0", diff --git a/public/index.html b/public/index.html index bd669818..2aefbb9a 100644 --- a/public/index.html +++ b/public/index.html @@ -24,7 +24,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - Rocket Bootcamp Project + Pixfolio App diff --git a/src/components/AdminUpload.js b/src/components/AdminUpload.js index 609acdc2..9b7902b3 100644 --- a/src/components/AdminUpload.js +++ b/src/components/AdminUpload.js @@ -12,6 +12,11 @@ import AutorenewIcon from "@mui/icons-material/Autorenew"; import SendIcon from "@mui/icons-material/Send"; import MuiAlert from "@mui/material/Alert"; +//Firebase +import { push, ref as databaseRef, set } from "firebase/database"; +import { getDownloadURL, ref as storageRef, uploadBytes} from "firebase/storage"; +import { database, storage } from "../firebase"; + function AdminUpload(props) { const [name, setName] = useState(""); const [email, setEmail] = useState(""); @@ -19,9 +24,17 @@ function AdminUpload(props) { const [files, setFiles] = useState([]); const [openSuccessSnackbar, setOpenSuccessSnackbar] = useState(false); const [openErrorSnackbar, setOpenErrorSnackbar] = useState(false); - // 1. Function to set state on the files upon dropping + const IMAGEOBJECT_FOLDER_NAME = "imageObjects"; + const IMAGES_FOLDER_NAME = "images"; //Images folder name + //Function: Upload time + const uploadDateTime = () => { + const uploadData = new Date(); + const formattedDate = uploadData.toLocaleDateString(); + const formattedTime = uploadData.toLocaleTimeString(); + return ("["+formattedDate + " "+ formattedTime +"]") + } // 2. Perform your form submission logic here, along with file upload handling const Alert = React.forwardRef(function Alert(props, ref) { @@ -42,6 +55,37 @@ function AdminUpload(props) { // Access the selected files from the "files" state and include them in your form data // Perform form validation before submission if (isFormValid()) { + + + // 'file' comes from the Blob or File API; On Click: Send file to firebase + // For loop throught the array of uploaded files + files.forEach(file => { + // Handle if the file is null + if (file !=null){ + // PART A: Upload file into storage + let fileRef = storageRef(storage, `${IMAGES_FOLDER_NAME}/${file.name}`); + // PART B: Upload ImageObject + // Uploading object for posts to the Firebase if there is a file + uploadBytes(fileRef, file).then(() => { + getDownloadURL(fileRef).then((downloadUrl) => { //get download URL for the given file + + //Writing data into the database + const postListRef = databaseRef(database, IMAGEOBJECT_FOLDER_NAME); + const newPostRef = push(postListRef); + set(newPostRef, { //set this into the posts + imgurl: downloadUrl, //1. download url + imgTime: file.lastModified, // 2. Image Metadata-Time + tagsarray: null, //3. No tags (default) + email: email, //4. User Email + name: name.toLowerCase(), //5. User's Name + pass: pass, //6. Unique Identifier + updatetime: uploadDateTime(), //5. Upload time (Admin) + }) + } + ) + }) + }}); + // Submit the form setName(""); //Reset the form setEmail(""); //Reset the form diff --git a/src/components/ResponsiveAppBar.js b/src/components/ResponsiveAppBar.js index d352430d..8384128e 100644 --- a/src/components/ResponsiveAppBar.js +++ b/src/components/ResponsiveAppBar.js @@ -11,7 +11,7 @@ import Avatar from '@mui/material/Avatar'; import Button from '@mui/material/Button'; import Tooltip from '@mui/material/Tooltip'; import MenuItem from '@mui/material/MenuItem'; -import AdbIcon from '@mui/icons-material/Adb'; +import CameraIcon from '@mui/icons-material/Camera'; const pages = ['About Us']; const settings = ['Profile', 'Account', 'Dashboard', 'Logout']; @@ -39,7 +39,7 @@ function ResponsiveAppBar() { - + - + - LOGO + PIXFOLIO {pages.map((page) => ( diff --git a/src/firebase.js b/src/firebase.js new file mode 100644 index 00000000..6d18323e --- /dev/null +++ b/src/firebase.js @@ -0,0 +1,30 @@ +// Import the functions you need from the SDKs you need +import { initializeApp } from "firebase/app"; +import { getDatabase } from "firebase/database"; +import { getStorage } from "firebase/storage"; +import { getAuth } from "firebase/auth"; + +// TODO: Replace with your app's Firebase project configuration +const firebaseConfig = { + databaseURL: process.env.REACT_APP_DATABASE_URL, + apiKey: process.env.REACT_APP_API_KEY, + authDomain: process.env.REACT_APP_AUTH_DOMAIN, + projectId: process.env.REACT_APP_PROJECT_ID, + storageBucket: process.env.REACT_APP_STORAGE_BUCKET, + messagingSenderId: process.env.REACT_APP_MESSAGE_SENDER_ID, + appId: process.env.REACT_APP_APP_ID, + measurementId: process.env.REACT_APP_MEASUREMENT_ID, + // The value of `databaseURL` depends on the location of the database +}; + + +// Initialize Firebase +const firebaseApp = initializeApp(firebaseConfig); + +// Get a reference to the database service and export the reference for other modules +// Service reference +export const database = getDatabase(firebaseApp); +export const storage = getStorage(firebaseApp); +export const auth = getAuth(firebaseApp); + + diff --git a/src/logo.png b/src/logo.png deleted file mode 100755 index a7e08590c47cb0b6df6329095b691602fe8a8a26..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29578 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 From fa7c9095054019f0f827a1579c9081a0dcfdcb46 Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Tue, 20 Jun 2023 21:06:27 +0800 Subject: [PATCH 06/35] resolved empty zip file issue --- src/App.css | 5 +- src/components/AdminUpload.js | 2 +- src/components/Home.js | 101 +++++++++++++++------------------- src/components/ImgDownload.js | 64 +++++++++++++-------- 4 files changed, 90 insertions(+), 82 deletions(-) diff --git a/src/App.css b/src/App.css index 9962555a..98a21314 100644 --- a/src/App.css +++ b/src/App.css @@ -4,7 +4,6 @@ .App-header { background-color: white; - min-height: 100vh; display: flex; flex-direction: column; align-items: center; @@ -78,3 +77,7 @@ width: auto; height: 100%; } + +.download-button{ + margin-top:30px; +} \ No newline at end of file diff --git a/src/components/AdminUpload.js b/src/components/AdminUpload.js index 9b7902b3..73b8f26a 100644 --- a/src/components/AdminUpload.js +++ b/src/components/AdminUpload.js @@ -183,7 +183,7 @@ function AdminUpload(props) { useEffect(() => { // Make sure to revoke the data uris to avoid memory leaks, will run on unmount return () => files.forEach((file) => URL.revokeObjectURL(file.preview)); - }, []); + }, [files]); return (
    diff --git a/src/components/Home.js b/src/components/Home.js index 582cc06f..02f0ef88 100644 --- a/src/components/Home.js +++ b/src/components/Home.js @@ -1,62 +1,50 @@ -import React from "react"; +import React,{useEffect,useState} from "react"; import ImageTile from "./ImageTile"; import SearchBar from "./SearchBar"; import ImgDownload from "./ImgDownload"; -class App extends React.Component { - render() { - //To be replaced by image data from server - const itemData = [ - { - img: "https://images.unsplash.com/photo-1551963831-b3b1ca40c98e", - title: "Breakfast", - }, - { - img: "https://images.unsplash.com/photo-1551782450-a2132b4ba21d", - title: "Burger", - }, - { - img: "https://images.unsplash.com/photo-1522770179533-24471fcdba45", - title: "Camera", - }, - { - img: "https://images.unsplash.com/photo-1444418776041-9c7e33cc5a9c", - title: "Coffee", - }, - { - img: "https://images.unsplash.com/photo-1533827432537-70133748f5c8", - title: "Hats", - }, - { - img: "https://images.unsplash.com/photo-1558642452-9d2a7deb7f62", - title: "Honey", - author: "@arwinneil", - }, - { - img: "https://images.unsplash.com/photo-1516802273409-68526ee1bdd6", - title: "Basketball", - }, - { - img: "https://images.unsplash.com/photo-1518756131217-31eb79b20e8f", - title: "Fern", - }, - { - img: "https://images.unsplash.com/photo-1597645587822-e99fa5d45d25", - title: "Mushrooms", - }, - { - img: "https://images.unsplash.com/photo-1567306301408-9b74779a11af", - title: "Tomato basil", - }, - { - img: "https://images.unsplash.com/photo-1471357674240-e1a485acb3e1", - title: "Sea star", - }, - { - img: "https://images.unsplash.com/photo-1589118949245-7d38baf380d6", - title: "Bike", - }, - ]; +//Firebase - Pull data from server +import { onChildAdded, ref as databaseRef } from "firebase/database"; +import { database} from "../firebase"; + +const IMAGEOBJECT_FOLDER_NAME = "imageObjects"; + +const Home = () => { + const [imageObjects, setImageObjects] = useState([]); + + useEffect(() => { + + // This effect will run when the component mounts and whenever the 'yourCollection' data changes in Firebase. + // You can perform any necessary operations here, such as updating state, manipulating the data, etc. + // Make sure to handle any cleanup if required (return a cleanup function). + const imgListRef = databaseRef(database, IMAGEOBJECT_FOLDER_NAME); + // Subscribe to the Firebase listener + //console.log(imgListRef); + + return () => { + // This code will run when the component unmounts + // You can perform any necessary cleanup here + onChildAdded(imgListRef, (data) => { + // Add the subsequent child to local component state, initialising a new array to trigger re-render + //console.log(data.val().imgurl) + setImageObjects((preImageObjects) => + // Store message key so we can use it as a key in our list items when rendering messages + [...preImageObjects, + { key: data.key, + imgurl: data.val().imgurl, + tagsarray: data.val().tagsarray, + email:data.val().email, + name:data.val().name, + pass:data.val().pass, + }], //key-value pair + )}); + }; + + },[]); + + // console.log(imageObjects) + const itemData = imageObjects.map(({key,imgurl}) => ({key:key,img:imgurl,title:null})); + return (
    @@ -71,6 +59,5 @@ class App extends React.Component {
    ); } -} -export default App; +export default Home; diff --git a/src/components/ImgDownload.js b/src/components/ImgDownload.js index c33fcc46..1ed99d52 100644 --- a/src/components/ImgDownload.js +++ b/src/components/ImgDownload.js @@ -1,42 +1,60 @@ import React, { useState } from "react"; import JSZip from "jszip"; import { saveAs } from "file-saver"; +import {Button} from "@mui/material"; +import DownloadIcon from '@mui/icons-material/Download'; + +//Firebase +const IMAGES_FOLDER_NAME = "images"; //Images folder name const ImgDownload = (props) => { const [imgUrl, setimgUrl] = useState(null); - const handleDownload = (imageUrls) => { + const downloadFilesAsZip = async (fileUrls) => { const zip = new JSZip(); - // const imageUrls = ['image1.jpg', 'image2.png', 'image3.gif']; // Replace with the URLs or file paths of your images - const addImagesToZip = async () => { - const imagePromises = imgUrl.map((url) => { - return fetch(url) - .then((response) => response.blob()) - .then((blob) => { - const filename = url.substring(url.lastIndexOf("/") + 1); - zip.file(filename, blob); - }); - }); - - await Promise.all(imagePromises); - const zipBlob = await zip.generateAsync({ type: "blob" }); - saveAs(zipBlob, "pixfolio-images.zip"); - }; + + // Loop through the array of file URLs + for (let i = 0; i < fileUrls.length; i++) { + const fileUrl = fileUrls[i]; + try { + // Fetch each file as an array buffer + const response = await fetch(fileUrl); + const fileData = await response.arrayBuffer(); + + // Get the file name from the URL + const fileName = decodeURIComponent(fileUrl.split('images%2F')[1].split('?')[0]); + + // Add the file to the ZIP + zip.file(fileName, fileData); + } catch (error) { + console.log(`Error downloading file at ${fileUrl}:`, error); + } + } - addImagesToZip(); - }; + try { + // Generate the ZIP file + const zipBlob = await zip.generateAsync({ type: 'blob' }); + + // Save the ZIP file + saveAs(zipBlob, 'files.zip'); + } catch (error) { + console.log('Error generating ZIP file:', error); + }} const mapDownload = () => { //map into an arrage of img links for download const objectList = props.ImageObjects; - const imageUrls = objectList.map((imageObject) => imageObject.img); - setimgUrl(imageUrls); //set the state for downloads - handleDownload(imageUrls); + const urlArray = objectList.map(({ img }) => img); + console.log(urlArray); + setimgUrl(urlArray); //set the state for downloads + downloadFilesAsZip(urlArray); }; return ( -
    - +
    +
    ); }; From 52710b916836ac83c1a277bb7fbb55cd5632b108 Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Mon, 26 Jun 2023 19:37:35 +0530 Subject: [PATCH 07/35] added image card component, added chip mui and box clicking text input --- src/App.css | 21 +++++++ src/components/AdminUpload.js | 14 ++++- src/components/ImageCard.js | 114 ++++++++++++++++++++++++++++++++++ src/components/ImageTile.js | 30 ++------- 4 files changed, 152 insertions(+), 27 deletions(-) create mode 100644 src/components/ImageCard.js diff --git a/src/App.css b/src/App.css index 98a21314..02f44f71 100644 --- a/src/App.css +++ b/src/App.css @@ -80,4 +80,25 @@ .download-button{ margin-top:30px; +} + +.overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; +} + +.overlay input { + width: 200px; + padding: 10px; + font-size: 16px; + border: none; + border-radius: 5px; + background-color: white; } \ No newline at end of file diff --git a/src/components/AdminUpload.js b/src/components/AdminUpload.js index 73b8f26a..376495b7 100644 --- a/src/components/AdminUpload.js +++ b/src/components/AdminUpload.js @@ -59,14 +59,15 @@ function AdminUpload(props) { // 'file' comes from the Blob or File API; On Click: Send file to firebase // For loop throught the array of uploaded files - files.forEach(file => { + files.forEach((file,index) => { // Handle if the file is null if (file !=null){ // PART A: Upload file into storage let fileRef = storageRef(storage, `${IMAGES_FOLDER_NAME}/${file.name}`); // PART B: Upload ImageObject // Uploading object for posts to the Firebase if there is a file - uploadBytes(fileRef, file).then(() => { + uploadBytes(fileRef, file) + .then(() => { getDownloadURL(fileRef).then((downloadUrl) => { //get download URL for the given file //Writing data into the database @@ -83,7 +84,14 @@ function AdminUpload(props) { }) } ) - }) + console.log(`Uploading ${file.name} ...`); + } + + ) + .catch((error) => { + // Handle upload error + console.log('Error uploading data:', error); + }); }}); // Submit the form diff --git a/src/components/ImageCard.js b/src/components/ImageCard.js new file mode 100644 index 00000000..6f64d119 --- /dev/null +++ b/src/components/ImageCard.js @@ -0,0 +1,114 @@ +import React from "react"; +import ImageListItem from "@mui/material/ImageListItem"; +import { styled } from '@mui/material/styles'; +import Chip from '@mui/material/Chip'; +import Box from '@mui/material/Box'; + +const ListItem = styled('li')(({ theme }) => ({ + margin: theme.spacing(0.5), +})); + + +export default function ImageTile(props) { + //Function: Takes in the image props and display them + const [chipData, setChipData] = React.useState([ + { key: 0, label: 'Angular' }, + { key: 1, label: 'jQuery' }, + { key: 2, label: 'Polymer' }, + { key: 3, label: 'React' }, + ]); + const [showInput, setShowInput]=React.useState(false); + const [inputValue, setInputValue] = React.useState(''); + const inputRef = React.useRef(null); + + const handleInputChange = (event) => { + setInputValue(event.target.value); //Updating the texts of the component + }; + + const handleImageClick = () => { + setShowInput(true); + }; + + const handleDelete = (chipToDelete) => () => { + setChipData((chips) => chips.filter((chip) => chip.key !== chipToDelete.key)); + }; + + const handleClickOutside = (event) => { + if (inputRef.current && !inputRef.current.contains(event.target)) { + setShowInput(false); + } + }; + + React.useEffect(() => { + document.addEventListener('click', handleClickOutside); + return () => { + document.removeEventListener('click', handleClickOutside); + }; + }, []); + + + //function to actually setup the sizes and image details for the tiling + function srcset(image, size, rows = 1, cols = 1) { + return { + src: `${image}?w=${size * cols}&h=${size * rows}&fit=crop&auto=format`, + srcSet: `${image}?w=${size * cols}&h=${ + size * rows + }&fit=crop&auto=format&dpr=2 2x`, + }; + } + + return ( + + {props.item.title} + {showInput && ( +
    + +
    + )} + + + {chipData.map((data) => { //this data will be replaced by component tagging + return ( + + + + ); +} +)} + + +
    + ); +} + diff --git a/src/components/ImageTile.js b/src/components/ImageTile.js index ca8950cc..14c8bf4c 100644 --- a/src/components/ImageTile.js +++ b/src/components/ImageTile.js @@ -1,37 +1,19 @@ import React from "react"; import ImageList from "@mui/material/ImageList"; -import ImageListItem from "@mui/material/ImageListItem"; +import ImageCard from './ImageCard'; + export default function ImageTile(props) { //Function: Takes in the image props and display them - - //function to actually setup the sizes and image details for the tiling - function srcset(image, size, rows = 1, cols = 1) { - return { - src: `${image}?w=${size * cols}&h=${size * rows}&fit=crop&auto=format`, - srcSet: `${image}?w=${size * cols}&h=${ - size * rows - }&fit=crop&auto=format&dpr=2 2x`, - }; - } - - return ( + return (
    + {props.ImageObjects.map((item) => ( - - {item.title} - + ))}
    ); } + From 04244a0ef2b65ea4a21d8b52b38a5b714d4a0d3c Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Fri, 30 Jun 2023 00:29:10 +0530 Subject: [PATCH 08/35] added working off picture clicking toggle off --- src/App.css | 7 ++++++- src/components/ImageCard.js | 14 ++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/App.css b/src/App.css index 02f44f71..403e929c 100644 --- a/src/App.css +++ b/src/App.css @@ -94,11 +94,16 @@ align-items: center; } -.overlay input { +.overlay-on input { width: 200px; padding: 10px; font-size: 16px; border: none; border-radius: 5px; background-color: white; +} + + +.overlay-off { + visibility: hidden; } \ No newline at end of file diff --git a/src/components/ImageCard.js b/src/components/ImageCard.js index 6f64d119..eced1f3e 100644 --- a/src/components/ImageCard.js +++ b/src/components/ImageCard.js @@ -20,13 +20,15 @@ export default function ImageTile(props) { const [showInput, setShowInput]=React.useState(false); const [inputValue, setInputValue] = React.useState(''); const inputRef = React.useRef(null); + const imgRef = React.useRef(null); const handleInputChange = (event) => { setInputValue(event.target.value); //Updating the texts of the component }; const handleImageClick = () => { - setShowInput(true); + console.log("Image clicked") + setShowInput(!showInput); }; const handleDelete = (chipToDelete) => () => { @@ -34,9 +36,12 @@ export default function ImageTile(props) { }; const handleClickOutside = (event) => { - if (inputRef.current && !inputRef.current.contains(event.target)) { + if (imgRef.current && !imgRef.current.contains(event.target) && inputRef.current && !inputRef.current.contains(event.target)) + { + console.log('clickedout') + console.log(showInput) setShowInput(false); - } + } }; React.useEffect(() => { @@ -44,7 +49,7 @@ export default function ImageTile(props) { return () => { document.removeEventListener('click', handleClickOutside); }; - }, []); + }); //function to actually setup the sizes and image details for the tiling @@ -67,6 +72,7 @@ export default function ImageTile(props) { {...srcset(props.item.img, 720, props.item.rows, props.item.cols)} alt={props.item.title} loading="lazy" + ref = {imgRef} onClick={handleImageClick} /> {showInput && ( From d0e744e1aea71baf42391aff43a839424c0d77f4 Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Sun, 2 Jul 2023 02:02:42 +0800 Subject: [PATCH 09/35] added condition for null array --- src/components/Home.js | 81 ++++++++------- src/components/ImageCard.js | 153 +++++++++++++++-------------- src/components/ImgDownload.js | 37 ++++--- src/components/ResponsiveAppBar.js | 87 ++++++++-------- 4 files changed, 190 insertions(+), 168 deletions(-) diff --git a/src/components/Home.js b/src/components/Home.js index 02f0ef88..f5fff041 100644 --- a/src/components/Home.js +++ b/src/components/Home.js @@ -1,11 +1,11 @@ -import React,{useEffect,useState} from "react"; +import React, { useEffect, useState } from "react"; import ImageTile from "./ImageTile"; import SearchBar from "./SearchBar"; import ImgDownload from "./ImgDownload"; //Firebase - Pull data from server import { onChildAdded, ref as databaseRef } from "firebase/database"; -import { database} from "../firebase"; +import { database } from "../firebase"; const IMAGEOBJECT_FOLDER_NAME = "imageObjects"; @@ -13,51 +13,66 @@ const Home = () => { const [imageObjects, setImageObjects] = useState([]); useEffect(() => { - // This effect will run when the component mounts and whenever the 'yourCollection' data changes in Firebase. // You can perform any necessary operations here, such as updating state, manipulating the data, etc. // Make sure to handle any cleanup if required (return a cleanup function). const imgListRef = databaseRef(database, IMAGEOBJECT_FOLDER_NAME); // Subscribe to the Firebase listener //console.log(imgListRef); - + return () => { // This code will run when the component unmounts // You can perform any necessary cleanup here onChildAdded(imgListRef, (data) => { // Add the subsequent child to local component state, initialising a new array to trigger re-render //console.log(data.val().imgurl) - setImageObjects((preImageObjects) => - // Store message key so we can use it as a key in our list items when rendering messages - [...preImageObjects, - { key: data.key, - imgurl: data.val().imgurl, - tagsarray: data.val().tagsarray, - email:data.val().email, - name:data.val().name, - pass:data.val().pass, - }], //key-value pair - )}); + setImageObjects( + (preImageObjects) => + // Store message key so we can use it as a key in our list items when rendering messages + [ + ...preImageObjects, + { + key: data.key, + imgurl: data.val().imgurl, //from fdb + tagsarray: data.val().tagsarray, //from fdb + email: data.val().email, //from fdb + name: data.val().name, //from fdb + pass: data.val().pass, //from fdb + }, + ] //key-value pair + ); + }); }; - - },[]); + }, []); // console.log(imageObjects) - const itemData = imageObjects.map(({key,imgurl}) => ({key:key,img:imgurl,title:null})); - - - return ( -
    - - -
    - -
    - -
    -
    -
    - ); - } + // extrating the key and the image only for downloading + const itemData = imageObjects.map( + ({ key, imgurl, tagsarray, email, name, pass }) => ({ + key: key, + imgurl: imgurl, + tagsarray: tagsarray, + email: email, + name: name, + pass: pass, + title: null, + }) + ); + + return ( +
    + + {console.log(itemData)} + {console.log(imageObjects)} + +
    + +
    + +
    +
    +
    + ); +}; export default Home; diff --git a/src/components/ImageCard.js b/src/components/ImageCard.js index eced1f3e..99348020 100644 --- a/src/components/ImageCard.js +++ b/src/components/ImageCard.js @@ -1,57 +1,56 @@ import React from "react"; import ImageListItem from "@mui/material/ImageListItem"; -import { styled } from '@mui/material/styles'; -import Chip from '@mui/material/Chip'; -import Box from '@mui/material/Box'; +import { styled } from "@mui/material/styles"; +import Chip from "@mui/material/Chip"; +import Box from "@mui/material/Box"; -const ListItem = styled('li')(({ theme }) => ({ +const ListItem = styled("li")(({ theme }) => ({ margin: theme.spacing(0.5), })); - export default function ImageTile(props) { //Function: Takes in the image props and display them - const [chipData, setChipData] = React.useState([ - { key: 0, label: 'Angular' }, - { key: 1, label: 'jQuery' }, - { key: 2, label: 'Polymer' }, - { key: 3, label: 'React' }, - ]); - const [showInput, setShowInput]=React.useState(false); - const [inputValue, setInputValue] = React.useState(''); + const [chipData, setChipData] = React.useState([]); + const [showInput, setShowInput] = React.useState(false); + const [inputValue, setInputValue] = React.useState(""); const inputRef = React.useRef(null); const imgRef = React.useRef(null); const handleInputChange = (event) => { setInputValue(event.target.value); //Updating the texts of the component }; - + const handleImageClick = () => { - console.log("Image clicked") + console.log("Image clicked"); setShowInput(!showInput); }; const handleDelete = (chipToDelete) => () => { - setChipData((chips) => chips.filter((chip) => chip.key !== chipToDelete.key)); + setChipData((chips) => + chips.filter((chip) => chip.key !== chipToDelete.key) + ); }; const handleClickOutside = (event) => { - if (imgRef.current && !imgRef.current.contains(event.target) && inputRef.current && !inputRef.current.contains(event.target)) - { - console.log('clickedout') - console.log(showInput) + if ( + imgRef.current && + !imgRef.current.contains(event.target) && + inputRef.current && + !inputRef.current.contains(event.target) + ) { + console.log("clickedout"); + console.log(showInput); setShowInput(false); - } + } }; React.useEffect(() => { - document.addEventListener('click', handleClickOutside); + document.addEventListener("click", handleClickOutside); return () => { - document.removeEventListener('click', handleClickOutside); + document.removeEventListener("click", handleClickOutside); }; }); - //function to actually setup the sizes and image details for the tiling function srcset(image, size, rows = 1, cols = 1) { return { @@ -62,59 +61,63 @@ export default function ImageTile(props) { }; } - return ( - - {props.item.title} - {showInput && ( -
    - -
    - )} - - - {chipData.map((data) => { //this data will be replaced by component tagging return ( - - + {props.item.title} - - ); -} -)} - + {showInput && ( +
    + +
    + )} -
    + + {props.item.tagsarray !== null + ? chipData.map((data) => { + //this data will be replaced by component tagging + return ( + + + + ); + }) + : null} + + ); } - diff --git a/src/components/ImgDownload.js b/src/components/ImgDownload.js index 1ed99d52..41ecf0c9 100644 --- a/src/components/ImgDownload.js +++ b/src/components/ImgDownload.js @@ -1,8 +1,8 @@ import React, { useState } from "react"; import JSZip from "jszip"; import { saveAs } from "file-saver"; -import {Button} from "@mui/material"; -import DownloadIcon from '@mui/icons-material/Download'; +import { Button } from "@mui/material"; +import DownloadIcon from "@mui/icons-material/Download"; //Firebase const IMAGES_FOLDER_NAME = "images"; //Images folder name @@ -12,7 +12,7 @@ const ImgDownload = (props) => { const downloadFilesAsZip = async (fileUrls) => { const zip = new JSZip(); - + // Loop through the array of file URLs for (let i = 0; i < fileUrls.length; i++) { const fileUrl = fileUrls[i]; @@ -20,10 +20,12 @@ const ImgDownload = (props) => { // Fetch each file as an array buffer const response = await fetch(fileUrl); const fileData = await response.arrayBuffer(); - + // Get the file name from the URL - const fileName = decodeURIComponent(fileUrl.split('images%2F')[1].split('?')[0]); - + const fileName = decodeURIComponent( + fileUrl.split("images%2F")[1].split("?")[0] + ); + // Add the file to the ZIP zip.file(fileName, fileData); } catch (error) { @@ -33,18 +35,19 @@ const ImgDownload = (props) => { try { // Generate the ZIP file - const zipBlob = await zip.generateAsync({ type: 'blob' }); - + const zipBlob = await zip.generateAsync({ type: "blob" }); + // Save the ZIP file - saveAs(zipBlob, 'files.zip'); + saveAs(zipBlob, "files.zip"); } catch (error) { - console.log('Error generating ZIP file:', error); - }} + console.log("Error generating ZIP file:", error); + } + }; const mapDownload = () => { //map into an arrage of img links for download const objectList = props.ImageObjects; - const urlArray = objectList.map(({ img }) => img); + const urlArray = objectList.map(({ imgurl }) => imgurl); console.log(urlArray); setimgUrl(urlArray); //set the state for downloads downloadFilesAsZip(urlArray); @@ -52,9 +55,13 @@ const ImgDownload = (props) => { return (
    - +
    ); }; diff --git a/src/components/ResponsiveAppBar.js b/src/components/ResponsiveAppBar.js index 8384128e..4724a8ba 100644 --- a/src/components/ResponsiveAppBar.js +++ b/src/components/ResponsiveAppBar.js @@ -1,20 +1,20 @@ -import * as React from 'react'; -import AppBar from '@mui/material/AppBar'; -import Box from '@mui/material/Box'; -import Toolbar from '@mui/material/Toolbar'; -import IconButton from '@mui/material/IconButton'; -import Typography from '@mui/material/Typography'; -import Menu from '@mui/material/Menu'; -import MenuIcon from '@mui/icons-material/Menu'; -import Container from '@mui/material/Container'; -import Avatar from '@mui/material/Avatar'; -import Button from '@mui/material/Button'; -import Tooltip from '@mui/material/Tooltip'; -import MenuItem from '@mui/material/MenuItem'; -import CameraIcon from '@mui/icons-material/Camera'; +import * as React from "react"; +import AppBar from "@mui/material/AppBar"; +import Box from "@mui/material/Box"; +import Toolbar from "@mui/material/Toolbar"; +import IconButton from "@mui/material/IconButton"; +import Typography from "@mui/material/Typography"; +import Menu from "@mui/material/Menu"; +import MenuIcon from "@mui/icons-material/Menu"; +import Container from "@mui/material/Container"; +import Avatar from "@mui/material/Avatar"; +import Button from "@mui/material/Button"; +import Tooltip from "@mui/material/Tooltip"; +import MenuItem from "@mui/material/MenuItem"; +import CameraIcon from "@mui/icons-material/Camera"; -const pages = ['About Us']; -const settings = ['Profile', 'Account', 'Dashboard', 'Logout']; +const pages = ["About Us"]; +const settings = ["Logout"]; function ResponsiveAppBar() { const [anchorElNav, setAnchorElNav] = React.useState(null); @@ -39,7 +39,7 @@ function ResponsiveAppBar() { - + PIXFOLIO - + {pages.map((page) => ( @@ -94,10 +94,9 @@ function ResponsiveAppBar() { ))} - - - + + PIXFOLIO - + {pages.map((page) => ( @@ -135,17 +134,17 @@ function ResponsiveAppBar() { Date: Sun, 2 Jul 2023 16:34:12 +0800 Subject: [PATCH 10/35] added chips into input --- src/components/AdminUpload.js | 86 ++++++++++++++----------- src/components/ImageCard.js | 100 ++++++++++++++++++++++++++--- src/components/ResponsiveAppBar.js | 2 +- 3 files changed, 139 insertions(+), 49 deletions(-) diff --git a/src/components/AdminUpload.js b/src/components/AdminUpload.js index 376495b7..fe292963 100644 --- a/src/components/AdminUpload.js +++ b/src/components/AdminUpload.js @@ -14,7 +14,11 @@ import MuiAlert from "@mui/material/Alert"; //Firebase import { push, ref as databaseRef, set } from "firebase/database"; -import { getDownloadURL, ref as storageRef, uploadBytes} from "firebase/storage"; +import { + getDownloadURL, + ref as storageRef, + uploadBytes, +} from "firebase/storage"; import { database, storage } from "../firebase"; function AdminUpload(props) { @@ -25,7 +29,7 @@ function AdminUpload(props) { const [openSuccessSnackbar, setOpenSuccessSnackbar] = useState(false); const [openErrorSnackbar, setOpenErrorSnackbar] = useState(false); // 1. Function to set state on the files upon dropping - const IMAGEOBJECT_FOLDER_NAME = "imageObjects"; + const IMAGEOBJECT_FOLDER_NAME = "imageObjects"; const IMAGES_FOLDER_NAME = "images"; //Images folder name //Function: Upload time @@ -33,8 +37,8 @@ function AdminUpload(props) { const uploadData = new Date(); const formattedDate = uploadData.toLocaleDateString(); const formattedTime = uploadData.toLocaleTimeString(); - return ("["+formattedDate + " "+ formattedTime +"]") - } + return "[" + formattedDate + " " + formattedTime + "]"; + }; // 2. Perform your form submission logic here, along with file upload handling const Alert = React.forwardRef(function Alert(props, ref) { @@ -55,44 +59,48 @@ function AdminUpload(props) { // Access the selected files from the "files" state and include them in your form data // Perform form validation before submission if (isFormValid()) { + // 'file' comes from the Blob or File API; On Click: Send file to firebase + // For loop throught the array of uploaded files + files.forEach((file, index) => { + // Handle if the file is null + if (file != null) { + // PART A: Upload file into storage + let fileRef = storageRef( + storage, + `${IMAGES_FOLDER_NAME}/${file.name}` + ); + // PART B: Upload ImageObject + // Uploading object for posts to the Firebase if there is a file + uploadBytes(fileRef, file) + .then(() => { + getDownloadURL(fileRef).then((downloadUrl) => { + //get download URL for the given file - - // 'file' comes from the Blob or File API; On Click: Send file to firebase - // For loop throught the array of uploaded files - files.forEach((file,index) => { - // Handle if the file is null - if (file !=null){ - // PART A: Upload file into storage - let fileRef = storageRef(storage, `${IMAGES_FOLDER_NAME}/${file.name}`); - // PART B: Upload ImageObject - // Uploading object for posts to the Firebase if there is a file - uploadBytes(fileRef, file) - .then(() => { - getDownloadURL(fileRef).then((downloadUrl) => { //get download URL for the given file - - //Writing data into the database - const postListRef = databaseRef(database, IMAGEOBJECT_FOLDER_NAME); - const newPostRef = push(postListRef); - set(newPostRef, { //set this into the posts - imgurl: downloadUrl, //1. download url - imgTime: file.lastModified, // 2. Image Metadata-Time - tagsarray: null, //3. No tags (default) - email: email, //4. User Email - name: name.toLowerCase(), //5. User's Name - pass: pass, //6. Unique Identifier - updatetime: uploadDateTime(), //5. Upload time (Admin) - }) + //Writing data into the database + const postListRef = databaseRef( + database, + IMAGEOBJECT_FOLDER_NAME + ); + const newPostRef = push(postListRef); + set(newPostRef, { + //set this into the posts + imgurl: downloadUrl, //1. download url + imgTime: file.lastModified, // 2. Image Metadata-Time + tagsarray: [{ key: 1, label: "default" }], //3. No tags (default) + email: email, //4. User Email + name: name.toLowerCase(), //5. User's Name + pass: pass, //6. Unique Identifier + updatetime: uploadDateTime(), //5. Upload time (Admin) + }); + }); + console.log(`Uploading ${file.name} ...`); + }) + .catch((error) => { + // Handle upload error + console.log("Error uploading data:", error); + }); } - ) - console.log(`Uploading ${file.name} ...`); - } - - ) - .catch((error) => { - // Handle upload error - console.log('Error uploading data:', error); }); - }}); // Submit the form setName(""); //Reset the form diff --git a/src/components/ImageCard.js b/src/components/ImageCard.js index 99348020..013d3c08 100644 --- a/src/components/ImageCard.js +++ b/src/components/ImageCard.js @@ -3,6 +3,8 @@ import ImageListItem from "@mui/material/ImageListItem"; import { styled } from "@mui/material/styles"; import Chip from "@mui/material/Chip"; import Box from "@mui/material/Box"; +import { database } from "../firebase"; +import { ref as databaseRef, update } from "firebase/database"; const ListItem = styled("li")(({ theme }) => ({ margin: theme.spacing(0.5), @@ -10,22 +12,26 @@ const ListItem = styled("li")(({ theme }) => ({ export default function ImageTile(props) { //Function: Takes in the image props and display them - const [chipData, setChipData] = React.useState([]); + const [chipData, setChipData] = React.useState(props.item.tagsarray); //Initial empty array const [showInput, setShowInput] = React.useState(false); const [inputValue, setInputValue] = React.useState(""); const inputRef = React.useRef(null); const imgRef = React.useRef(null); + // 1. Function to set state on the files upon dropping + const IMAGEOBJECT_FOLDER_NAME = "imageObjects"; + const handleInputChange = (event) => { setInputValue(event.target.value); //Updating the texts of the component }; const handleImageClick = () => { - console.log("Image clicked"); + console.log(`Image clicked: ${props.item.key}`); setShowInput(!showInput); }; const handleDelete = (chipToDelete) => () => { + console.log(chipToDelete); setChipData((chips) => chips.filter((chip) => chip.key !== chipToDelete.key) ); @@ -41,6 +47,61 @@ export default function ImageTile(props) { console.log("clickedout"); console.log(showInput); setShowInput(false); + setInputValue(""); //reset the input + } + }; + + //This function updates the new object array for the chip + const addChipFormat = (chipValue) => { + let arrayData = [...chipData]; //copy value + chipValue = chipValue.replace(/\s/g, ""); + console.log(`Array Chip Length: ${arrayData.length}`); + if (arrayData.length !== 0) { + //if not empty + let lastKeyValue = arrayData[arrayData.length - 1].key; + let objectAppend = { + key: lastKeyValue + 1, //running number + label: chipValue.toLowerCase(), + }; + arrayData.push(objectAppend); //appends to the object array + console.log(arrayData); + return arrayData; + } else { + return [ + { + key: 1, + label: chipValue.toLowerCase(), + }, + ]; + } + }; + + //This handles the keyboard enter key to register submission + const handleKeyPress = (event) => { + if (event.key === "Enter") { + // Perform any necessary logic here + console.log(inputValue); + if (inputValue !== "") { + setChipData(addChipFormat(inputValue)); //append to the chip data + + //Writing data into the database + const objectPath = IMAGEOBJECT_FOLDER_NAME + "/" + props.item.key; + const postListRef = databaseRef(database, objectPath); + console.log(`Path: ${objectPath}`); + console.log(`postListRef: ${postListRef}`); + // Update the parameter to firebase + update(postListRef, { tagsarray: addChipFormat(inputValue) }) + .then(() => { + console.log("Chips updated successfully"); + }) + .catch((error) => { + console.error("Error updating Chips:", error); + }); + + setShowInput(false); + setInputValue(""); //reset the input + } else { + } } }; @@ -51,6 +112,23 @@ export default function ImageTile(props) { }; }); + React.useEffect(() => { + console.log(`chipData: ${chipData}`); + // Writing data into the database + const objectPath = `${IMAGEOBJECT_FOLDER_NAME}/${props.item.key}`; + const postListRef = databaseRef(database, objectPath); + console.log(`Path: ${objectPath}`); + console.log(`postListRef: ${postListRef}`); + // Update the parameter to Firebase + update(postListRef, { tagsarray: chipData }) + .then(() => { + console.log("Chips updated successfully"); + }) + .catch((error) => { + console.error("Error updating Chips:", error); + }); + }, [chipData]); + //function to actually setup the sizes and image details for the tiling function srcset(image, size, rows = 1, cols = 1) { return { @@ -80,7 +158,9 @@ export default function ImageTile(props) { ref={inputRef} type="text" value={inputValue} + maxlength="8" onChange={handleInputChange} + onKeyPress={handleKeyPress} />
    )} @@ -106,13 +186,15 @@ export default function ImageTile(props) { //this data will be replaced by component tagging return ( - + {data.label !== "default" && ( //hide default chip label but retain it + + )} ); }) diff --git a/src/components/ResponsiveAppBar.js b/src/components/ResponsiveAppBar.js index 4724a8ba..35330553 100644 --- a/src/components/ResponsiveAppBar.js +++ b/src/components/ResponsiveAppBar.js @@ -101,7 +101,7 @@ function ResponsiveAppBar() { variant="h5" noWrap component="a" - href="" + href="/" sx={{ mr: 2, display: { xs: "flex", md: "none" }, From d1144b9fb83e9cc3c51e90e0291b183a78ace3aa Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Sun, 2 Jul 2023 19:55:05 +0800 Subject: [PATCH 11/35] added filtering capabiltiies but errors still remain on resetting state --- src/components/Home.js | 47 +++++++++++++++++++++++++++++++++---- src/components/SearchBar.js | 15 ++++++++---- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/components/Home.js b/src/components/Home.js index f5fff041..4fbcb482 100644 --- a/src/components/Home.js +++ b/src/components/Home.js @@ -11,6 +11,7 @@ const IMAGEOBJECT_FOLDER_NAME = "imageObjects"; const Home = () => { const [imageObjects, setImageObjects] = useState([]); + const [filteredData, setFilteredData] = useState([]); useEffect(() => { // This effect will run when the component mounts and whenever the 'yourCollection' data changes in Firebase. @@ -41,13 +42,29 @@ const Home = () => { }, ] //key-value pair ); + + setFilteredData( + (preImageObjects) => + // Store message key so we can use it as a key in our list items when rendering messages + [ + ...preImageObjects, + { + key: data.key, + imgurl: data.val().imgurl, //from fdb + tagsarray: data.val().tagsarray, //from fdb + email: data.val().email, //from fdb + name: data.val().name, //from fdb + pass: data.val().pass, //from fdb + }, + ] //key-value pair + ); }); }; }, []); // console.log(imageObjects) - // extrating the key and the image only for downloading - const itemData = imageObjects.map( + // extracting the key and the image only for downloading + const itemData = filteredData.map( ({ key, imgurl, tagsarray, email, name, pass }) => ({ key: key, imgurl: imgurl, @@ -59,11 +76,33 @@ const Home = () => { }) ); + //Function that filters data based on input + const filterData = (searchTerm) => { + const keywords = searchTerm.toLowerCase().split(" "); //split by spaces + + if (searchTerm === "") { + //if empty text return keyword filter will be default + const filteredData = itemData.filter((item) => { + // Check if any hobby has the category "Art" + return item.tagsarray.some((tags) => tags.label === "default"); + }); + setFilteredData(filteredData); + } else { + for (const element of keywords) { + const filteredData = itemData.filter((item) => { + // Check if any hobby has the category "Art" + return item.tagsarray.some((tags) => tags.label === element); + }); + setFilteredData(filteredData); + } + } + }; + return (
    - + {console.log(itemData)} - {console.log(imageObjects)} + {console.log(`Image Objects: ${JSON.stringify(imageObjects)}`)}
    diff --git a/src/components/SearchBar.js b/src/components/SearchBar.js index 9d1a89f7..647218c1 100644 --- a/src/components/SearchBar.js +++ b/src/components/SearchBar.js @@ -1,12 +1,17 @@ -import { Container, InputAdornment, TextField } from "@mui/material"; +import { Button, Container, InputAdornment, TextField } from "@mui/material"; import { useState } from "react"; import SearchIcon from "@mui/icons-material/Search"; -export default function SearchBar() { +export default function SearchBar({ onSearch }) { const [searchTerm, setSearchTerm] = useState(""); const handleChange = (event) => { setSearchTerm(event.target.value); + console.log(searchTerm); + }; + + const handleSearch = () => { + onSearch(searchTerm); }; return ( @@ -21,11 +26,13 @@ export default function SearchBar() { InputProps={{ endAdornment: ( - + ), }} /> ); -} \ No newline at end of file +} From 765489c9ca639e09da2fa160a9b1d1bd0447d7b3 Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Mon, 3 Jul 2023 11:32:38 +0800 Subject: [PATCH 12/35] added working filtering code. some issue with the mui chips persists after filtering, wrong overlay --- src/components/Home.js | 21 ++++++++++++--------- src/components/SearchBar.js | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/components/Home.js b/src/components/Home.js index 4fbcb482..4b75a0b6 100644 --- a/src/components/Home.js +++ b/src/components/Home.js @@ -64,8 +64,8 @@ const Home = () => { // console.log(imageObjects) // extracting the key and the image only for downloading - const itemData = filteredData.map( - ({ key, imgurl, tagsarray, email, name, pass }) => ({ + useEffect(() => { + const itemData = filteredData.map(({ key, imgurl, tagsarray, email, name, pass }) => ({ key: key, imgurl: imgurl, tagsarray: tagsarray, @@ -73,8 +73,11 @@ const Home = () => { name: name, pass: pass, title: null, - }) - ); + })); + + setImageObjects(itemData); + console.log(`Initial Filtered Data: ${filteredData}`) + }, []); //Function that filters data based on input const filterData = (searchTerm) => { @@ -82,14 +85,14 @@ const Home = () => { if (searchTerm === "") { //if empty text return keyword filter will be default - const filteredData = itemData.filter((item) => { + const filteredData = imageObjects.filter((item) => { // Check if any hobby has the category "Art" return item.tagsarray.some((tags) => tags.label === "default"); }); setFilteredData(filteredData); } else { for (const element of keywords) { - const filteredData = itemData.filter((item) => { + const filteredData = imageObjects.filter((item) => { // Check if any hobby has the category "Art" return item.tagsarray.some((tags) => tags.label === element); }); @@ -101,13 +104,13 @@ const Home = () => { return (
    - {console.log(itemData)} + {console.log(filteredData)} {console.log(`Image Objects: ${JSON.stringify(imageObjects)}`)} - +
    - +
    diff --git a/src/components/SearchBar.js b/src/components/SearchBar.js index 647218c1..172c3658 100644 --- a/src/components/SearchBar.js +++ b/src/components/SearchBar.js @@ -7,10 +7,10 @@ export default function SearchBar({ onSearch }) { const handleChange = (event) => { setSearchTerm(event.target.value); - console.log(searchTerm); }; const handleSearch = () => { + console.log(`Searching Keyword: ${searchTerm}`); onSearch(searchTerm); }; From 99d16a1a3efcb424e0eb231bf68293d5aa6484c4 Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Tue, 4 Jul 2023 03:13:22 +0800 Subject: [PATCH 13/35] resolved overlappting tags on mui chips --- src/components/Home.js | 16 ++++++++++------ src/components/ImageCard.js | 19 +++++++++++++------ src/components/ImageTile.js | 1 + 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/components/Home.js b/src/components/Home.js index 4b75a0b6..eaacd338 100644 --- a/src/components/Home.js +++ b/src/components/Home.js @@ -10,8 +10,9 @@ import { database } from "../firebase"; const IMAGEOBJECT_FOLDER_NAME = "imageObjects"; const Home = () => { - const [imageObjects, setImageObjects] = useState([]); - const [filteredData, setFilteredData] = useState([]); + const [imageObjects, setImageObjects] = useState([]); //State 1 + const [filteredData, setFilteredData] = useState([]); //State 2 + const [searchPressed, setSearchPressed] = useState([false]); useEffect(() => { // This effect will run when the component mounts and whenever the 'yourCollection' data changes in Firebase. @@ -65,7 +66,7 @@ const Home = () => { // console.log(imageObjects) // extracting the key and the image only for downloading useEffect(() => { - const itemData = filteredData.map(({ key, imgurl, tagsarray, email, name, pass }) => ({ + const itemData = imageObjects.map(({ key, imgurl, tagsarray, email, name, pass }) => ({ key: key, imgurl: imgurl, tagsarray: tagsarray, @@ -82,7 +83,7 @@ const Home = () => { //Function that filters data based on input const filterData = (searchTerm) => { const keywords = searchTerm.toLowerCase().split(" "); //split by spaces - + setSearchPressed(!searchPressed) if (searchTerm === "") { //if empty text return keyword filter will be default const filteredData = imageObjects.filter((item) => { @@ -99,13 +100,16 @@ const Home = () => { setFilteredData(filteredData); } } + //Also assists to save the chips }; return ( +
    + {console.log(`Filtered Data: ${JSON.stringify(filteredData)}`)} - {console.log(filteredData)} - {console.log(`Image Objects: ${JSON.stringify(imageObjects)}`)} + {/* {console.log(filteredData)} */} + {/* {console.log(`Image Objects: ${JSON.stringify(imageObjects)}`)} */}
    diff --git a/src/components/ImageCard.js b/src/components/ImageCard.js index 013d3c08..b00581d6 100644 --- a/src/components/ImageCard.js +++ b/src/components/ImageCard.js @@ -12,7 +12,7 @@ const ListItem = styled("li")(({ theme }) => ({ export default function ImageTile(props) { //Function: Takes in the image props and display them - const [chipData, setChipData] = React.useState(props.item.tagsarray); //Initial empty array + const [chipData, setChipData] = React.useState([]); //Initial empty array const [showInput, setShowInput] = React.useState(false); const [inputValue, setInputValue] = React.useState(""); const inputRef = React.useRef(null); @@ -32,6 +32,7 @@ export default function ImageTile(props) { const handleDelete = (chipToDelete) => () => { console.log(chipToDelete); + console.log(`Deleted From: ${JSON.stringify(props.item)}`) setChipData((chips) => chips.filter((chip) => chip.key !== chipToDelete.key) ); @@ -83,7 +84,6 @@ export default function ImageTile(props) { console.log(inputValue); if (inputValue !== "") { setChipData(addChipFormat(inputValue)); //append to the chip data - //Writing data into the database const objectPath = IMAGEOBJECT_FOLDER_NAME + "/" + props.item.key; const postListRef = databaseRef(database, objectPath); @@ -112,14 +112,19 @@ export default function ImageTile(props) { }; }); + React.useEffect(()=>{ //whenever the props.item.tagsarray changes, it will update state + setChipData(props.item.tagsarray) + },[props.item.tagsarray]) + React.useEffect(() => { - console.log(`chipData: ${chipData}`); + // console.log(`chipData: ${chipData}`); // Writing data into the database const objectPath = `${IMAGEOBJECT_FOLDER_NAME}/${props.item.key}`; const postListRef = databaseRef(database, objectPath); - console.log(`Path: ${objectPath}`); - console.log(`postListRef: ${postListRef}`); + // console.log(`Path: ${objectPath}`); + // console.log(`postListRef: ${postListRef}`); // Update the parameter to Firebase + // Whenever chipdata changes it updates server update(postListRef, { tagsarray: chipData }) .then(() => { console.log("Chips updated successfully"); @@ -127,7 +132,7 @@ export default function ImageTile(props) { .catch((error) => { console.error("Error updating Chips:", error); }); - }, [chipData]); + }, [chipData,props.item.key]); //function to actually setup the sizes and image details for the tiling function srcset(image, size, rows = 1, cols = 1) { @@ -184,6 +189,8 @@ export default function ImageTile(props) { {props.item.tagsarray !== null ? chipData.map((data) => { //this data will be replaced by component tagging + console.log(`Data Received: ${JSON.stringify(props.item)}`) + console.log(`Chip Data: ${JSON.stringify(data)}`) return ( {data.label !== "default" && ( //hide default chip label but retain it diff --git a/src/components/ImageTile.js b/src/components/ImageTile.js index 14c8bf4c..e892593e 100644 --- a/src/components/ImageTile.js +++ b/src/components/ImageTile.js @@ -5,6 +5,7 @@ import ImageCard from './ImageCard'; export default function ImageTile(props) { //Function: Takes in the image props and display them + console.log("Image Tile Rendered") return (
    From 95d3ea1880bebca067a917c96dc6e0b6c9c06c4d Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Wed, 5 Jul 2023 03:40:22 +0800 Subject: [PATCH 14/35] resolved bug on the chip tagging. resolved issue on the imageObjects stacking and missing assignment. added mui styes for text input --- src/components/Home.js | 91 +++++++++++++++---------------------- src/components/ImageCard.js | 32 +++++++++---- src/components/SearchBar.js | 2 +- 3 files changed, 60 insertions(+), 65 deletions(-) diff --git a/src/components/Home.js b/src/components/Home.js index eaacd338..80e7cb24 100644 --- a/src/components/Home.js +++ b/src/components/Home.js @@ -11,8 +11,7 @@ const IMAGEOBJECT_FOLDER_NAME = "imageObjects"; const Home = () => { const [imageObjects, setImageObjects] = useState([]); //State 1 - const [filteredData, setFilteredData] = useState([]); //State 2 - const [searchPressed, setSearchPressed] = useState([false]); + const [filterTerms, setfilterTerms] = useState([]); useEffect(() => { // This effect will run when the component mounts and whenever the 'yourCollection' data changes in Firebase. @@ -26,6 +25,7 @@ const Home = () => { // This code will run when the component unmounts // You can perform any necessary cleanup here onChildAdded(imgListRef, (data) => { + console.log(`Server Data: ${JSON.stringify(data)}`); // Add the subsequent child to local component state, initialising a new array to trigger re-render //console.log(data.val().imgurl) setImageObjects( @@ -41,80 +41,63 @@ const Home = () => { name: data.val().name, //from fdb pass: data.val().pass, //from fdb }, - ] //key-value pair - ); - - setFilteredData( - (preImageObjects) => - // Store message key so we can use it as a key in our list items when rendering messages - [ - ...preImageObjects, - { - key: data.key, - imgurl: data.val().imgurl, //from fdb - tagsarray: data.val().tagsarray, //from fdb - email: data.val().email, //from fdb - name: data.val().name, //from fdb - pass: data.val().pass, //from fdb - }, - ] //key-value pair + ].map(({ key, imgurl, tagsarray, email, name, pass }) => ({ + key: key, + imgurl: imgurl, + tagsarray: tagsarray, + email: email, + name: name, + pass: pass, + title: null, + })) //key-value pair ); }); }; - }, []); + }, [filterTerms]); // console.log(imageObjects) // extracting the key and the image only for downloading - useEffect(() => { - const itemData = imageObjects.map(({ key, imgurl, tagsarray, email, name, pass }) => ({ - key: key, - imgurl: imgurl, - tagsarray: tagsarray, - email: email, - name: name, - pass: pass, - title: null, - })); - - setImageObjects(itemData); - console.log(`Initial Filtered Data: ${filteredData}`) - }, []); - //Function that filters data based on input - const filterData = (searchTerm) => { + const updateTerms = (searchTerm) => { const keywords = searchTerm.toLowerCase().split(" "); //split by spaces - setSearchPressed(!searchPressed) - if (searchTerm === "") { - //if empty text return keyword filter will be default - const filteredData = imageObjects.filter((item) => { - // Check if any hobby has the category "Art" - return item.tagsarray.some((tags) => tags.label === "default"); - }); - setFilteredData(filteredData); + setImageObjects([]); //reset data when search is clicked + if (keywords[0] === "") { + setfilterTerms([]); //when user search empty string } else { - for (const element of keywords) { + setfilterTerms(keywords); + } + }; + + //Function that filters data based on input + const filterData = (searchArray) => { + console.log(searchArray); + if (searchArray.length > 0) { + for (const element of searchArray) { const filteredData = imageObjects.filter((item) => { // Check if any hobby has the category "Art" return item.tagsarray.some((tags) => tags.label === element); }); - setFilteredData(filteredData); + + console.log(`Filtered Data: ${JSON.stringify(filteredData)}`); + + return filteredData; } + } else { + return imageObjects; } - //Also assists to save the chips }; return ( -
    - {console.log(`Filtered Data: ${JSON.stringify(filteredData)}`)} - - {/* {console.log(filteredData)} */} - {/* {console.log(`Image Objects: ${JSON.stringify(imageObjects)}`)} */} - + + {console.log(`Search Terms: ${filterTerms}`)} + {console.log(`Image Objects: ${JSON.stringify(imageObjects)}`)} + {console.log(`input: ${JSON.stringify(filterData(filterTerms))}`)} +
    - +
    diff --git a/src/components/ImageCard.js b/src/components/ImageCard.js index b00581d6..a91f6c5f 100644 --- a/src/components/ImageCard.js +++ b/src/components/ImageCard.js @@ -5,6 +5,7 @@ import Chip from "@mui/material/Chip"; import Box from "@mui/material/Box"; import { database } from "../firebase"; import { ref as databaseRef, update } from "firebase/database"; +import TextField from "@mui/material/TextField"; const ListItem = styled("li")(({ theme }) => ({ margin: theme.spacing(0.5), @@ -32,7 +33,7 @@ export default function ImageTile(props) { const handleDelete = (chipToDelete) => () => { console.log(chipToDelete); - console.log(`Deleted From: ${JSON.stringify(props.item)}`) + console.log(`Deleted From: ${JSON.stringify(props.item)}`); setChipData((chips) => chips.filter((chip) => chip.key !== chipToDelete.key) ); @@ -112,9 +113,10 @@ export default function ImageTile(props) { }; }); - React.useEffect(()=>{ //whenever the props.item.tagsarray changes, it will update state - setChipData(props.item.tagsarray) - },[props.item.tagsarray]) + React.useEffect(() => { + //whenever the props.item.tagsarray changes, it will update state + setChipData(props.item.tagsarray); + }, [props.item.tagsarray]); React.useEffect(() => { // console.log(`chipData: ${chipData}`); @@ -132,7 +134,7 @@ export default function ImageTile(props) { .catch((error) => { console.error("Error updating Chips:", error); }); - }, [chipData,props.item.key]); + }, [chipData, props.item.key]); //function to actually setup the sizes and image details for the tiling function srcset(image, size, rows = 1, cols = 1) { @@ -150,6 +152,7 @@ export default function ImageTile(props) { cols={props.item.cols || 1} rows={props.item.rows || 1} > + {/* the number like 720 in changes the img quality */} {props.item.title} {showInput && (
    -
    @@ -189,8 +201,8 @@ export default function ImageTile(props) { {props.item.tagsarray !== null ? chipData.map((data) => { //this data will be replaced by component tagging - console.log(`Data Received: ${JSON.stringify(props.item)}`) - console.log(`Chip Data: ${JSON.stringify(data)}`) + console.log(`Data Received: ${JSON.stringify(props.item)}`); + console.log(`Chip Data: ${JSON.stringify(data)}`); return ( {data.label !== "default" && ( //hide default chip label but retain it diff --git a/src/components/SearchBar.js b/src/components/SearchBar.js index 172c3658..06a280a0 100644 --- a/src/components/SearchBar.js +++ b/src/components/SearchBar.js @@ -22,7 +22,7 @@ export default function SearchBar({ onSearch }) { label="Search Tags" value={searchTerm} onChange={handleChange} - sx={{ width: 600 }} + fullWidth InputProps={{ endAdornment: ( From e2b6ccb0eb41e56b847839d9612507f9690cb870 Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Fri, 7 Jul 2023 03:10:25 +0800 Subject: [PATCH 15/35] Added login page --- src/App.js | 54 +++++++-- src/components/Auth.js | 81 +++++++++++++ src/components/Login.js | 100 ++++++++++++++++ src/components/LoginDefault.js | 177 +++++++++++++++++++++++++++++ src/components/ResponsiveAppBar.js | 9 +- 5 files changed, 410 insertions(+), 11 deletions(-) create mode 100644 src/components/Auth.js create mode 100644 src/components/Login.js create mode 100644 src/components/LoginDefault.js diff --git a/src/App.js b/src/App.js index a58360d9..a9b4920e 100644 --- a/src/App.js +++ b/src/App.js @@ -6,23 +6,57 @@ import { BrowserRouter, Route, Routes } from "react-router-dom"; //routes import AdminUpload from "./components/AdminUpload"; import Home from "./components/Home"; +import SignIn from "./components/LoginDefault"; +import Auth from "./components/Auth"; +import { getAuth, onAuthStateChanged, signOut } from "firebase/auth"; -class App extends React.Component { - render() { - return ( - +const App = () => { + const auth = getAuth(); + const [loggedInUser, setLoggedInUser] = React.useState(null); + + React.useEffect(() => { + const unsubscribe = onAuthStateChanged(auth, (user) => { + console.log("Auth State Triggered"); + if (user) { + console.log(`User Registered: ${user}`); + setLoggedInUser(user); + } else { + setLoggedInUser(null); + } + }); + + return () => { + unsubscribe(); + }; + }, []); + + const signOut = () => { + signOut(auth) + .then(() => { + console.log("Signed Out"); + setLoggedInUser(null); + }) + .catch((error) => { + console.log("Error signing out:", error); + }); + }; + + return ( + +
    - +
    - } /> + : } /> } /> + } />
    -
    - ); - } -} + +
    + ); +}; export default App; diff --git a/src/components/Auth.js b/src/components/Auth.js new file mode 100644 index 00000000..d904c8ae --- /dev/null +++ b/src/components/Auth.js @@ -0,0 +1,81 @@ +import { + GoogleAuthProvider, + createUserWithEmailAndPassword, + onAuthStateChanged, + signInWithEmailAndPassword, + signInWithPopup, + signOut, +} from "firebase/auth"; +import { auth } from "../firebase"; +import React, { useEffect } from "react"; +import { useState } from "react"; +import { useContext, createContext } from "react"; + +const authContext = createContext(); + +// react hook to handle user authentication +export const useAuth = () => { + return useContext(authContext); +}; + +// google auth, will open popup window to choose google account + +const loginWithGoogle = () => { + const provider = new GoogleAuthProvider(); + return signInWithPopup(auth, provider); +}; + +const Auth = ({ children }) => { + // create state for the current user and pass to other components + const [currentUser, setCurrentUser] = useState(null); + const [modal, setModal] = useState({ + isOpen: false, + title: "", + content: "", + }); + + // show error message + const [alert, setAlert] = useState({ + isAlert: false, + severity: "info", + message: "", + timeout: null, + location: "", + }); + + const signUp = (email, password) => { + return createUserWithEmailAndPassword(auth, email, password); + }; + + const login = (email, password) => { + return signInWithEmailAndPassword(auth, email, password); + }; + + const logout = () => { + return signOut(auth); + }; + + // listener for any change in user state + useEffect(() => { + const unsubscribe = onAuthStateChanged(auth, (user) => { + setCurrentUser(user); + }); + return unsubscribe; + }, []); + const value = { + currentUser, + signUp, + login, + logout, + modal, + setModal, + loginWithGoogle, + alert, + setAlert, + }; + + // authContext.Provider push the value properties to children + return {children}; +}; + +export default Auth; diff --git a/src/components/Login.js b/src/components/Login.js new file mode 100644 index 00000000..3a733695 --- /dev/null +++ b/src/components/Login.js @@ -0,0 +1,100 @@ +import React, { useState, useEffect } from "react"; +import { Container, Typography, TextField, Button } from "@mui/material"; +import { useAuth } from "./Auth"; +import { useNavigate } from "react-router-dom"; + +const Login = () => { + const navigate = useNavigate(); + const { login, loginWithGoogle, currentUser, setAlert } = useAuth(); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + + useEffect(() => { + if (currentUser) { + console.log("Welcome " + currentUser.email); + } + }, [currentUser, navigate]); + + const handleEmailChange = (event) => { + setEmail(event.target.value); + }; + + const handlePasswordChange = (event) => { + setPassword(event.target.value); + }; + const handleLogin = async (e) => { + e.preventDefault(); + try { + await login(email, password); + navigate("/"); // Redirect to Home after successful login + } catch (error) { + setAlert({ + isAlert: true, + severity: "error", + message: error.message, + timeout: 5000, + location: "page", + }); + console.log(error); + } + }; + + const handleGoogleLogin = async () => { + try { + await loginWithGoogle(); + navigate("/"); // Redirect to Home after successful login + } catch (error) { + console.log("Google login error:", error); + } + }; + + return ( + +
    + + Login + +
    + + + + + +
    +
    + ); +}; + +export default Login; diff --git a/src/components/LoginDefault.js b/src/components/LoginDefault.js new file mode 100644 index 00000000..1cedbcb6 --- /dev/null +++ b/src/components/LoginDefault.js @@ -0,0 +1,177 @@ +import React, { useState, useEffect } from "react"; +import Avatar from "@mui/material/Avatar"; +import CssBaseline from "@mui/material/CssBaseline"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import Checkbox from "@mui/material/Checkbox"; +import Link from "@mui/material/Link"; +import Grid from "@mui/material/Grid"; +import Box from "@mui/material/Box"; +import LockOutlinedIcon from "@mui/icons-material/LockOutlined"; +import { createTheme, ThemeProvider } from "@mui/material/styles"; +import { Container, Typography, TextField, Button } from "@mui/material"; +import { useAuth } from "./Auth"; +import { useNavigate } from "react-router-dom"; +import GoogleIcon from "@mui/icons-material/Google"; + +function Copyright(props) { + return ( + + {"Copyright © "} + + Pixfolio + {" "} + {new Date().getFullYear()} + {"."} + + ); +} + +// TODO remove, this demo shouldn't need to reset the theme. + +const defaultTheme = createTheme(); + +export default function SignIn() { + const navigate = useNavigate(); + const { login, loginWithGoogle, currentUser, setAlert } = useAuth(); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + + useEffect(() => { + if (currentUser) { + console.log("Welcome " + currentUser.email); + } + }, [currentUser, navigate]); + + const handleEmailChange = (event) => { + setEmail(event.target.value); + }; + + const handlePasswordChange = (event) => { + setPassword(event.target.value); + }; + const handleLogin = async (e) => { + e.preventDefault(); + try { + await login(email, password); + navigate("/"); // Redirect to Home after successful login + } catch (error) { + setAlert({ + isAlert: true, + severity: "error", + message: error.message, + timeout: 5000, + location: "page", + }); + console.log(error); + } + }; + + const handleGoogleLogin = async () => { + try { + await loginWithGoogle(); + navigate("/"); // Redirect to Home after successful login + } catch (error) { + console.log("Google login error:", error); + } + }; + + const handleSubmit = (event) => { + event.preventDefault(); + const data = new FormData(event.currentTarget); + console.log({ + email: data.get("email"), + password: data.get("password"), + }); + }; + + return ( + + + + + + + + + Sign in + + + + + } + label="Remember me" + /> + + + + + + Forgot password? + + + + + {"Don't have an account? Sign Up"} + + + + + + + + + ); +} diff --git a/src/components/ResponsiveAppBar.js b/src/components/ResponsiveAppBar.js index 35330553..c8790048 100644 --- a/src/components/ResponsiveAppBar.js +++ b/src/components/ResponsiveAppBar.js @@ -12,11 +12,13 @@ import Button from "@mui/material/Button"; import Tooltip from "@mui/material/Tooltip"; import MenuItem from "@mui/material/MenuItem"; import CameraIcon from "@mui/icons-material/Camera"; +import { useAuth } from "./Auth"; const pages = ["About Us"]; const settings = ["Logout"]; function ResponsiveAppBar() { + const { currentUser, logout } = useAuth(); const [anchorElNav, setAnchorElNav] = React.useState(null); const [anchorElUser, setAnchorElUser] = React.useState(null); @@ -37,6 +39,8 @@ function ResponsiveAppBar() { return ( + {console.log(JSON.stringify(currentUser))} + {console.log(currentUser?.photoURL)} @@ -130,7 +134,10 @@ function ResponsiveAppBar() { - + Date: Fri, 7 Jul 2023 22:53:12 +0800 Subject: [PATCH 16/35] Added google sign-in capabitilies --- src/App.js | 18 +++-------- src/components/ResponsiveAppBar.js | 50 ++++++++++++++++-------------- src/components/SignOutButton.js | 26 ++++++++++++++++ 3 files changed, 58 insertions(+), 36 deletions(-) create mode 100644 src/components/SignOutButton.js diff --git a/src/App.js b/src/App.js index a9b4920e..7793d8eb 100644 --- a/src/App.js +++ b/src/App.js @@ -30,25 +30,17 @@ const App = () => { }; }, []); - const signOut = () => { - signOut(auth) - .then(() => { - console.log("Signed Out"); - setLoggedInUser(null); - }) - .catch((error) => { - console.log("Error signing out:", error); - }); - }; - return (
    - +
    - : } /> + : } + /> } /> } /> diff --git a/src/components/ResponsiveAppBar.js b/src/components/ResponsiveAppBar.js index c8790048..8b94b5e3 100644 --- a/src/components/ResponsiveAppBar.js +++ b/src/components/ResponsiveAppBar.js @@ -13,9 +13,9 @@ import Tooltip from "@mui/material/Tooltip"; import MenuItem from "@mui/material/MenuItem"; import CameraIcon from "@mui/icons-material/Camera"; import { useAuth } from "./Auth"; +import SignOutButton from "./SignOutButton"; -const pages = ["About Us"]; -const settings = ["Logout"]; +const pages = [""]; function ResponsiveAppBar() { const { currentUser, logout } = useAuth(); @@ -140,28 +140,32 @@ function ResponsiveAppBar() { /> - - {settings.map((setting) => ( - - {setting} + + {currentUser && ( + + + {`${currentUser.displayName}`} - ))} - + + + + + )} diff --git a/src/components/SignOutButton.js b/src/components/SignOutButton.js new file mode 100644 index 00000000..ba6114ef --- /dev/null +++ b/src/components/SignOutButton.js @@ -0,0 +1,26 @@ +import React from "react"; +import { getAuth, signOut } from "firebase/auth"; +import Typography from "@mui/material/Typography"; + +const SignOutButton = () => { + const handleSignOut = () => { + const auth = getAuth(); + signOut(auth) + .then(() => { + // Sign-out successful. + console.log("User signed out successfully"); + }) + .catch((error) => { + // An error happened. + console.error("Error occurred while signing out:", error); + }); + }; + + return ( + + {"Logout "} + + ); +}; + +export default SignOutButton; From 04b8f2c04ebc878cde357980453bbad3ef608507 Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Sat, 8 Jul 2023 09:15:50 +0800 Subject: [PATCH 17/35] added email and gmail logins --- src/App.js | 4 +- src/components/Login.js | 100 ---------------------- src/components/LoginDefault.js | 23 ++--- src/components/SignUpAccount.js | 146 ++++++++++++++++++++++++++++++++ 4 files changed, 161 insertions(+), 112 deletions(-) delete mode 100644 src/components/Login.js create mode 100644 src/components/SignUpAccount.js diff --git a/src/App.js b/src/App.js index 7793d8eb..3e1ac321 100644 --- a/src/App.js +++ b/src/App.js @@ -7,6 +7,7 @@ import { BrowserRouter, Route, Routes } from "react-router-dom"; import AdminUpload from "./components/AdminUpload"; import Home from "./components/Home"; import SignIn from "./components/LoginDefault"; +import SignUp from "./components/SignUpAccount"; import Auth from "./components/Auth"; import { getAuth, onAuthStateChanged, signOut } from "firebase/auth"; @@ -42,7 +43,8 @@ const App = () => { element={auth.currentUser ? : } /> } /> - } /> + } /> + } />
    diff --git a/src/components/Login.js b/src/components/Login.js deleted file mode 100644 index 3a733695..00000000 --- a/src/components/Login.js +++ /dev/null @@ -1,100 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { Container, Typography, TextField, Button } from "@mui/material"; -import { useAuth } from "./Auth"; -import { useNavigate } from "react-router-dom"; - -const Login = () => { - const navigate = useNavigate(); - const { login, loginWithGoogle, currentUser, setAlert } = useAuth(); - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - - useEffect(() => { - if (currentUser) { - console.log("Welcome " + currentUser.email); - } - }, [currentUser, navigate]); - - const handleEmailChange = (event) => { - setEmail(event.target.value); - }; - - const handlePasswordChange = (event) => { - setPassword(event.target.value); - }; - const handleLogin = async (e) => { - e.preventDefault(); - try { - await login(email, password); - navigate("/"); // Redirect to Home after successful login - } catch (error) { - setAlert({ - isAlert: true, - severity: "error", - message: error.message, - timeout: 5000, - location: "page", - }); - console.log(error); - } - }; - - const handleGoogleLogin = async () => { - try { - await loginWithGoogle(); - navigate("/"); // Redirect to Home after successful login - } catch (error) { - console.log("Google login error:", error); - } - }; - - return ( - -
    - - Login - -
    - - - - - -
    -
    - ); -}; - -export default Login; diff --git a/src/components/LoginDefault.js b/src/components/LoginDefault.js index 1cedbcb6..60a08e1c 100644 --- a/src/components/LoginDefault.js +++ b/src/components/LoginDefault.js @@ -12,6 +12,7 @@ import { Container, Typography, TextField, Button } from "@mui/material"; import { useAuth } from "./Auth"; import { useNavigate } from "react-router-dom"; import GoogleIcon from "@mui/icons-material/Google"; +import Alert from "@mui/material/Alert"; function Copyright(props) { return ( @@ -40,6 +41,7 @@ export default function SignIn() { const { login, loginWithGoogle, currentUser, setAlert } = useAuth(); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); + const [error, setError] = useState(null); useEffect(() => { if (currentUser) { @@ -60,6 +62,7 @@ export default function SignIn() { await login(email, password); navigate("/"); // Redirect to Home after successful login } catch (error) { + setError("Invalid email/password. Try again."); setAlert({ isAlert: true, severity: "error", @@ -76,6 +79,7 @@ export default function SignIn() { await loginWithGoogle(); navigate("/"); // Redirect to Home after successful login } catch (error) { + setError("Invalid email/password. Try again."); console.log("Google login error:", error); } }; @@ -107,12 +111,7 @@ export default function SignIn() { Sign in - + - } - label="Remember me" - /> + + {error && {error}} @@ -163,7 +164,7 @@ export default function SignIn() { - + {"Don't have an account? Sign Up"} diff --git a/src/components/SignUpAccount.js b/src/components/SignUpAccount.js new file mode 100644 index 00000000..8873ee8e --- /dev/null +++ b/src/components/SignUpAccount.js @@ -0,0 +1,146 @@ +import React, { useState, useEffect } from "react"; +import Avatar from "@mui/material/Avatar"; +import CssBaseline from "@mui/material/CssBaseline"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import Checkbox from "@mui/material/Checkbox"; +import Link from "@mui/material/Link"; +import Grid from "@mui/material/Grid"; +import Box from "@mui/material/Box"; +import LockOutlinedIcon from "@mui/icons-material/LockOutlined"; +import { createTheme, ThemeProvider } from "@mui/material/styles"; +import { Container, Typography, TextField, Button } from "@mui/material"; +import { useAuth } from "./Auth"; +import { useNavigate } from "react-router-dom"; +import { createUserWithEmailAndPassword } from "firebase/auth"; +import { auth } from "../firebase"; +import Alert from "@mui/material/Alert"; + +function Copyright(props) { + return ( + + {"Copyright © "} + + Pixfolio + {" "} + {new Date().getFullYear()} + {"."} + + ); +} + +// TODO remove, this demo shouldn't need to reset the theme. + +const defaultTheme = createTheme(); + +export default function SignUp() { + const navigate = useNavigate(); + const { login, loginWithGoogle, currentUser, setAlert } = useAuth(); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(null); + + useEffect(() => { + if (currentUser) { + console.log("Welcome " + currentUser.email); + } + }, [currentUser, navigate]); + + const handleEmailChange = (event) => { + setEmail(event.target.value); + }; + + const handlePasswordChange = (event) => { + setPassword(event.target.value); + }; + + const onSubmit = async (e) => { + e.preventDefault(); + + await createUserWithEmailAndPassword(auth, email, password) + .then((userCredential) => { + // Signed in + const user = userCredential.user; + console.log(user); + navigate("/"); + // ... + }) + .catch((error) => { + const errorCode = error.code; + const errorMessage = error.message; + setError(errorMessage); + console.log(errorCode, errorMessage); + // .. + }); + }; + + return ( + + + + + + + + + Sign Up + + + + + {error && {error}} + + + + + + {"Already have an account? Sign in!"} + + + + + + + + + ); +} From 4556f6c8cea7ddeccf3fcf6ec28b2067d3680705 Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Sat, 8 Jul 2023 09:59:16 +0800 Subject: [PATCH 18/35] added password recovery via firebase --- src/components/LoginDefault.js | 24 ++++++++++++++++++++---- src/components/ResetPassword.js | 20 ++++++++++++++++++++ src/components/ResponsiveAppBar.js | 4 ++-- src/components/SignUpAccount.js | 6 ++++-- 4 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 src/components/ResetPassword.js diff --git a/src/components/LoginDefault.js b/src/components/LoginDefault.js index 60a08e1c..0eb5358a 100644 --- a/src/components/LoginDefault.js +++ b/src/components/LoginDefault.js @@ -1,8 +1,6 @@ import React, { useState, useEffect } from "react"; import Avatar from "@mui/material/Avatar"; import CssBaseline from "@mui/material/CssBaseline"; -import FormControlLabel from "@mui/material/FormControlLabel"; -import Checkbox from "@mui/material/Checkbox"; import Link from "@mui/material/Link"; import Grid from "@mui/material/Grid"; import Box from "@mui/material/Box"; @@ -10,9 +8,11 @@ import LockOutlinedIcon from "@mui/icons-material/LockOutlined"; import { createTheme, ThemeProvider } from "@mui/material/styles"; import { Container, Typography, TextField, Button } from "@mui/material"; import { useAuth } from "./Auth"; +import { getAuth, sendPasswordResetEmail } from "firebase/auth"; import { useNavigate } from "react-router-dom"; import GoogleIcon from "@mui/icons-material/Google"; import Alert from "@mui/material/Alert"; +import { set } from "firebase/database"; function Copyright(props) { return ( @@ -42,6 +42,7 @@ export default function SignIn() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(null); + const [alertMsg, setAlertMsg] = useState(null); useEffect(() => { if (currentUser) { @@ -63,6 +64,7 @@ export default function SignIn() { navigate("/"); // Redirect to Home after successful login } catch (error) { setError("Invalid email/password. Try again."); + setAlertMsg(null); //reset the alert setAlert({ isAlert: true, severity: "error", @@ -80,6 +82,7 @@ export default function SignIn() { navigate("/"); // Redirect to Home after successful login } catch (error) { setError("Invalid email/password. Try again."); + setAlertMsg(null); //reset the alert console.log("Google login error:", error); } }; @@ -93,6 +96,19 @@ export default function SignIn() { }); }; + const triggerResetEmail = async (e) => { + e.preventDefault(); + try { + const auth = getAuth(); + await sendPasswordResetEmail(auth, email); + setAlertMsg("Please check your email to reset password."); + setError(null); + } catch (error) { + setError("Recovery Error: Email not recognized."); + setAlertMsg(null); + } + }; + return ( @@ -135,8 +151,8 @@ export default function SignIn() { autoComplete="current-password" onChange={handlePasswordChange} /> - {error && {error}} + {alertMsg && {alertMsg}} +
    + ); +} + +export default ResetPassword; diff --git a/src/components/ResponsiveAppBar.js b/src/components/ResponsiveAppBar.js index 8b94b5e3..b776dbc6 100644 --- a/src/components/ResponsiveAppBar.js +++ b/src/components/ResponsiveAppBar.js @@ -15,7 +15,7 @@ import CameraIcon from "@mui/icons-material/Camera"; import { useAuth } from "./Auth"; import SignOutButton from "./SignOutButton"; -const pages = [""]; +const pages = ["About", "Contact Us"]; function ResponsiveAppBar() { const { currentUser, logout } = useAuth(); @@ -132,7 +132,7 @@ function ResponsiveAppBar() { - + { + .then(async (userCredential) => { // Signed in const user = userCredential.user; + // Updating user name + await updateProfile(auth.currentUser, { displayName: "Default" }); console.log(user); navigate("/"); // ... From d80b3087d67bcfbf11f92ee8f37134d45cae502a Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Sat, 8 Jul 2023 12:22:06 +0800 Subject: [PATCH 19/35] added user email account filter --- src/components/Home.js | 15 +++++++++++---- src/components/ResetPassword.js | 20 -------------------- src/components/ResponsiveAppBar.js | 2 +- src/components/test.js | 0 4 files changed, 12 insertions(+), 25 deletions(-) delete mode 100644 src/components/ResetPassword.js create mode 100644 src/components/test.js diff --git a/src/components/Home.js b/src/components/Home.js index 80e7cb24..dd56c310 100644 --- a/src/components/Home.js +++ b/src/components/Home.js @@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react"; import ImageTile from "./ImageTile"; import SearchBar from "./SearchBar"; import ImgDownload from "./ImgDownload"; +import { useAuth } from "./Auth"; //Firebase - Pull data from server import { onChildAdded, ref as databaseRef } from "firebase/database"; @@ -12,6 +13,7 @@ const IMAGEOBJECT_FOLDER_NAME = "imageObjects"; const Home = () => { const [imageObjects, setImageObjects] = useState([]); //State 1 const [filterTerms, setfilterTerms] = useState([]); + const { currentUser } = useAuth(); useEffect(() => { // This effect will run when the component mounts and whenever the 'yourCollection' data changes in Firebase. @@ -70,20 +72,25 @@ const Home = () => { //Function that filters data based on input const filterData = (searchArray) => { - console.log(searchArray); + let searchList = []; + console.log(searchArray); //["mountain","purple"] + let emailFilter = imageObjects.filter( + (obj) => obj.email === currentUser.email + ); if (searchArray.length > 0) { for (const element of searchArray) { - const filteredData = imageObjects.filter((item) => { + const filteredData = emailFilter.filter((item) => { // Check if any hobby has the category "Art" return item.tagsarray.some((tags) => tags.label === element); }); console.log(`Filtered Data: ${JSON.stringify(filteredData)}`); - + searchList.push(filteredData[0]); return filteredData; } + console.log(`Final-List: ${JSON.stringify(searchList)}`); } else { - return imageObjects; + return emailFilter; } }; diff --git a/src/components/ResetPassword.js b/src/components/ResetPassword.js deleted file mode 100644 index d6f09d2c..00000000 --- a/src/components/ResetPassword.js +++ /dev/null @@ -1,20 +0,0 @@ -import { getAuth, sendPasswordResetEmail } from "firebase/auth"; -function ResetPassword() { - const [email, setEmail] = useState(""); - const auth = getAuth(); - - const triggerResetEmail = async () => { - await sendPasswordResetEmail(auth, email); - console.log("Password reset email sent"); - }; - - return ( -
    - -
    - ); -} - -export default ResetPassword; diff --git a/src/components/ResponsiveAppBar.js b/src/components/ResponsiveAppBar.js index b776dbc6..768e526a 100644 --- a/src/components/ResponsiveAppBar.js +++ b/src/components/ResponsiveAppBar.js @@ -18,7 +18,7 @@ import SignOutButton from "./SignOutButton"; const pages = ["About", "Contact Us"]; function ResponsiveAppBar() { - const { currentUser, logout } = useAuth(); + const { currentUser } = useAuth(); const [anchorElNav, setAnchorElNav] = React.useState(null); const [anchorElUser, setAnchorElUser] = React.useState(null); diff --git a/src/components/test.js b/src/components/test.js new file mode 100644 index 00000000..e69de29b From d7261a0a52e77a17e7cf0b77753158a64dbae344 Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Sat, 8 Jul 2023 14:47:18 +0800 Subject: [PATCH 20/35] added unique array filter --- src/components/Home.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/components/Home.js b/src/components/Home.js index dd56c310..8189ce42 100644 --- a/src/components/Home.js +++ b/src/components/Home.js @@ -62,7 +62,7 @@ const Home = () => { const updateTerms = (searchTerm) => { const keywords = searchTerm.toLowerCase().split(" "); //split by spaces - setImageObjects([]); //reset data when search is clicked + // setImageObjects([]); //reset data when search is clicked if (keywords[0] === "") { setfilterTerms([]); //when user search empty string } else { @@ -74,23 +74,31 @@ const Home = () => { const filterData = (searchArray) => { let searchList = []; console.log(searchArray); //["mountain","purple"] + //Email Filtering let emailFilter = imageObjects.filter( (obj) => obj.email === currentUser.email ); + //Duplicates Filtering + const uniqueArray = emailFilter.filter((obj, index, self) => { + // Use a temporary object to keep track of unique keys + return index === self.findIndex((o) => o.key === obj.key); + }); + //Looping through tags if (searchArray.length > 0) { for (const element of searchArray) { - const filteredData = emailFilter.filter((item) => { + const filteredData = uniqueArray.filter((item) => { // Check if any hobby has the category "Art" return item.tagsarray.some((tags) => tags.label === element); }); console.log(`Filtered Data: ${JSON.stringify(filteredData)}`); searchList.push(filteredData[0]); - return filteredData; + // return filteredData; } console.log(`Final-List: ${JSON.stringify(searchList)}`); + return searchList; } else { - return emailFilter; + return uniqueArray; } }; From 32e45ebfc660ec4a609958ab2aed5d62c5e65165 Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Sat, 8 Jul 2023 14:59:17 +0800 Subject: [PATCH 21/35] resolved pushing into list issue with ...spreader --- src/components/Home.js | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/components/Home.js b/src/components/Home.js index 8189ce42..1aef1c9b 100644 --- a/src/components/Home.js +++ b/src/components/Home.js @@ -79,10 +79,12 @@ const Home = () => { (obj) => obj.email === currentUser.email ); //Duplicates Filtering - const uniqueArray = emailFilter.filter((obj, index, self) => { - // Use a temporary object to keep track of unique keys - return index === self.findIndex((o) => o.key === obj.key); - }); + const uniqueArray = Object.values( + emailFilter.reduce((acc, obj) => { + acc[obj.key] = obj; + return acc; + }, {}) + ); //Looping through tags if (searchArray.length > 0) { for (const element of searchArray) { @@ -92,11 +94,17 @@ const Home = () => { }); console.log(`Filtered Data: ${JSON.stringify(filteredData)}`); - searchList.push(filteredData[0]); + searchList.push(...filteredData); // return filteredData; } console.log(`Final-List: ${JSON.stringify(searchList)}`); - return searchList; + const uniqueOutput = Object.values( + searchList.reduce((acc, obj) => { + acc[obj.key] = obj; + return acc; + }, {}) + ); + return uniqueOutput; } else { return uniqueArray; } From 3dd0b7ac1e7242fa3fe70f122b51b433ef45d95d Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Sun, 9 Jul 2023 00:42:27 +0800 Subject: [PATCH 22/35] added uploading of files sequentially with final success message on snack bar --- src/components/AdminUpload.js | 38 ++++++++++++++++++++++++++++++++--- src/components/test.js | 0 2 files changed, 35 insertions(+), 3 deletions(-) delete mode 100644 src/components/test.js diff --git a/src/components/AdminUpload.js b/src/components/AdminUpload.js index fe292963..7fae8a7c 100644 --- a/src/components/AdminUpload.js +++ b/src/components/AdminUpload.js @@ -28,6 +28,8 @@ function AdminUpload(props) { const [files, setFiles] = useState([]); const [openSuccessSnackbar, setOpenSuccessSnackbar] = useState(false); const [openErrorSnackbar, setOpenErrorSnackbar] = useState(false); + const [openInfoSnackbar, setOpenInfoSnackbar] = useState(false); + const [uploadingFiles, setUploadingFiles] = useState(""); // 1. Function to set state on the files upon dropping const IMAGEOBJECT_FOLDER_NAME = "imageObjects"; const IMAGES_FOLDER_NAME = "images"; //Images folder name @@ -53,12 +55,18 @@ function AdminUpload(props) { setOpenErrorSnackbar(false); }; + const handleCloseInfoSnackbar = () => { + setOpenInfoSnackbar(false); + }; + const handleSubmit = (event) => { event.preventDefault(); // Perform your form submission logic here, along with file upload handling // Access the selected files from the "files" state and include them in your form data // Perform form validation before submission if (isFormValid()) { + let successCounter = 0; // Counter for successful uploads + let totalFiles = files.length; // Total number of files // 'file' comes from the Blob or File API; On Click: Send file to firebase // For loop throught the array of uploaded files files.forEach((file, index) => { @@ -92,8 +100,20 @@ function AdminUpload(props) { pass: pass, //6. Unique Identifier updatetime: uploadDateTime(), //5. Upload time (Admin) }); + successCounter++; // Increment the counter for successful uploads + + setUploadingFiles( + `Uploading ${successCounter}/${totalFiles} Files...` + ); + setOpenInfoSnackbar(true); // Turn on snack bar + + if (successCounter === totalFiles) { + // All files have been successfully uploaded + setOpenSuccessSnackbar(true); + handleCloseErrorSnackbar(); + handleCloseInfoSnackbar(); + } }); - console.log(`Uploading ${file.name} ...`); }) .catch((error) => { // Handle upload error @@ -107,8 +127,7 @@ function AdminUpload(props) { setEmail(""); //Reset the form setPass(""); //Reset the form setFiles([]); //Reset the form - setOpenSuccessSnackbar(true); - setOpenErrorSnackbar(false); + console.log("Form submitted successfully"); console.log("Form submitted with files:", files); // Additional submission logic @@ -234,6 +253,19 @@ function AdminUpload(props) { Please fill in all fields! + + + {uploadingFiles} + + Date: Sun, 9 Jul 2023 01:01:13 +0800 Subject: [PATCH 23/35] updated the icon logo --- public/favicon.ico | Bin 15406 -> 16859 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/public/favicon.ico b/public/favicon.ico index c17bf23f2a9d89c137737f64a1c25eac3c88f5cc..a90e345608b6ecc2cbdb602062f839c1301d7e4d 100755 GIT binary patch literal 16859 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 literal 15406 zcmeHOS$7mg6mF0H0DSO0PaYn5@<}0(5Vo*G*f$jj5Eg;3?;_v=ay%lUfPk_D4hUg8 zED8b!#YDEq5(7yf%S7MBxW;ziv=k)3Bs#|rxx_#^3TeoTy z(P!K1Xz5C={kE^`DVfWn$pZB^?gXi@EJO#x0B7pX1kCQhnuY4@Zi zOiw*wDsRyGnmnaCYJ2o?<4&X6$GT6vw0UctI+ncqK}&7X{$u{;z`ps5I@=)(?AnHDk^F^1f41;-uRcXI5- zefQ=I{;(p@KCVGq@!jNp?L43THPGU(Wej-!gKgif2SL;NGu+k=FC$ufe?4ET?`Uh( zr=2qRx5t%Q>}i!R=pJ)S+9yuF@%sbjv8x%Tc-o@z6Ix?m!7uM#kV?FoToBh{zKJ^A zCr!2%6I=Zj*9x@QS@4)LwOaMPC`W8A&l6y?+OJi*^_w)iFY{l?>!>kft0^b9H6}*= zCL1xiRsQ|bTvXaDZ}7nQckcD6HgUvox4ITU`E$0DgV;A%?bi-nPOiV@8-qXAE)UDY zs!fJ{2LF>6?5_!|Vt~B`4_w*HYH1Y58_=`ECtGpFvZk=gA8U`>#i14q0*kmq7B6D& zt>E}_N4+WkUHcLre+(POx@O>OAcJw^<2&cgeVLZ)#%Zycs@VK+HdHfrnCh#~$OD_5Z4ro8<*wm2%`sI7O>W69X;Aj_ew&9wd$fJ_Vcmv#1czi zqchxezYBkd&7J?7{8Rh5Xvo+6(`9)fN*1XPVk6sG=Pc``>UvDV?$D01k z>4fr6Pj}Jz-vR_3M=sb1|KXqg>NcnGG4@Caj||cZ~2Ok3C|9 zTg~l)2Ko0PgI$CiklpzfD?Kn^j}bru{j7 zDyeOo*F^3k(fdrPEH^pV2khd)n2>Ls_P(auz&d7(o2GK?Dc0fBXV%b7&dr(SX-ezD z3C$N^j#irFh{QO`Sg&vnLe^>QEpGa}j-V%k&ghPDfrt2p@xDW*$RQi?mdnZ*C-$Ib zu)L9*JaN83-KUaijGpoRdNPlAKFy(AANHE{_U}c;0h_jF1M5zLbzgi3 z#A>&Tqt9cjV?x~RgzgCZutPZKW))vFI-L7LpQ3*!?h`l$6X*2w_z+Xz!k!iC?y`Kb zf1lqRf9mPsf*aJ-K>r!Q&p6;gc zy#I8!d2J)hAA5FA&Re}aum-@{706M>g?%Kf!Js$rsTe2rQ)D_kW8ONwzoQ?+VwSaj zk01Dj{sZSXdwlAd2bm831N-Fq{+xcto_K-It^hyIAme;dF<&^>gt$^(H$b1nTFq(9 zdVVKp&N*!f%E9;w*KP4F@J~4UW{WD=bB5st#jb&UybT{;v9~Xj-P8# zV{TdYIjRU;YM>o`1R7=(xX)HvTztJyg!;z9ZpA9sGqj+ZL@)C+m?r zx&F7YKbFT-P!v?{sgu~O@WOdL Date: Sat, 15 Jul 2023 12:17:59 +0800 Subject: [PATCH 24/35] Update README.md --- README.md | 86 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 77 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index fd1a195a..70803315 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,83 @@ -# Rocket Academy Coding Bootcamp: Project 2: Full-Stack App (Firebase) -https://bc.rocketacademy.co/2-full-stack/2.p-full-stack-app-firebase +# Pixfolio -## Available Scripts +An image gallery for photographers to upload pictures online and assign them to users. Users can login to review their images and assign tags for easy categorization and download. -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). In the project directory, you can run: +![Screenshot 2023-07-15 at 10-36-51 Pixfolio App](https://github.com/dexterch91/project2-bootcamp/assets/38061057/6ed84631-2c91-4307-8f79-ec7cde6eccc3) -### `npm start` +![Screenshot 2023-07-15 at 10-38-25 Pixfolio App](https://github.com/dexterch91/project2-bootcamp/assets/38061057/03d3ac42-ca78-4968-9cd1-01d1d396a7c0) -Runs the app in the development mode.\ -Open [http://localhost:3000](http://localhost:3000) to view it in your browser. +![Screenshot 2023-07-15 at 10-35-05 Pixfolio App](https://github.com/dexterch91/project2-bootcamp/assets/38061057/4ce71dcc-073d-4066-a029-ef7a802982cd) -The page will reload when you make changes.\ -You may also see any lint errors in the console. +# Features + +Upload screen which allows the admin to upload multiples images and tag it to users. + +Image gallery screen which allows the user to: +* Tag image with categories +* Filter images +* Download the images in zip file format + +# Tech Used +Front end: [React](https://react.dev) + +Routing: [React Router](https://reactrouter.com/en/main) + +UI: [MUI](https://mui.com) + +Storage/Database/Auth: [Firebase](https://firebase.google.com/docs) + +File Upload: [Dropzone](https://www.dropzone.dev) + +File Download: [file-saver](https://www.npmjs.com/package/file-saver), [jszip](https://stuk.github.io/jszip) + +# Installation +This project was bootstrapped with Create React App. In the project directory, run the following steps: + +1. Clone repo to local +2. Configure .env file and insert the API keys +``` +REACT_APP_API_KEY = +REACT_APP_AUTH_DOMAIN = +REACT_APP_DATABASE_URL = +REACT_APP_PROJECT_ID = +REACT_APP_STORAGE_BUCKET = +REACT_APP_MESSAGING_SENDER_ID = +REACT_APP_APP_ID = +``` + +3. Install the dependencies + + + +``` + npm install react + npm install react-router-dom + npm install react-dropzone + npm install jszip + npm install file-saver + npm install @mui/material @emotion/react @emotion/styled + npm install firebase + +``` +# Run the app in dev mode: +Open the terminal and render the app: +```bash +npm start +``` + +To view it in the browser: http://localhost:3000 + +# Usage: +## Admin +1. Upload images and tag to the user (jpg or png format only). +2. Generate unique password for the user + +## User +1. Once admin has uploaded the images, user can sign up with the unique password +2. User can login and see the images tagged to him +3. To categorize the image, double click on the image and enter the keywords +4. To remove the category, click 'X' on the image tooltip. +5. User may search all images to find images which fall under the respective categories. More than one category can be searched in one line, i.e. the search term 'sun mountain' will display images with 'sun' and/or 'mountain' tags. +6. User may download the images, which will be in stored in zip format. +7. User can logout once the process is completed. From 1b195d69cded1a4faf580af522ce3b8dea38476c Mon Sep 17 00:00:00 2001 From: Karina <38061057+kaucers@users.noreply.github.com> Date: Sat, 15 Jul 2023 12:21:45 +0800 Subject: [PATCH 25/35] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 70803315..99c37870 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ REACT_APP_PROJECT_ID = REACT_APP_STORAGE_BUCKET = REACT_APP_MESSAGING_SENDER_ID = REACT_APP_APP_ID = +REACT_APP_MEASUREMENT_ID = ``` 3. Install the dependencies From 6e838e1a1e69ed5598d185b033bf218db699d9a3 Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Tue, 18 Jul 2023 02:36:04 +0800 Subject: [PATCH 26/35] Added ADMIN USER email for upload --- src/App.js | 25 ++++++++++++++++--- src/components/ErrorPage.js | 49 +++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 src/components/ErrorPage.js diff --git a/src/App.js b/src/App.js index 3e1ac321..8e0d5689 100644 --- a/src/App.js +++ b/src/App.js @@ -1,7 +1,7 @@ import React from "react"; import "./App.css"; import ResponsiveAppBar from "./components/ResponsiveAppBar"; -import { BrowserRouter, Route, Routes } from "react-router-dom"; +import { BrowserRouter, Route, Routes, Navigate } from "react-router-dom"; //routes import AdminUpload from "./components/AdminUpload"; @@ -9,17 +9,34 @@ import Home from "./components/Home"; import SignIn from "./components/LoginDefault"; import SignUp from "./components/SignUpAccount"; import Auth from "./components/Auth"; +import ErrorPage from "./components/ErrorPage"; import { getAuth, onAuthStateChanged, signOut } from "firebase/auth"; +const PrivateAdminRoute = (user) => { + const ADMIN_USER = "speed6200@gmail.com"; + try { + // Code that may throw an error or exception + if (user.email === ADMIN_USER) { + return ; + } + } catch (error) { + // Code to handle the error + console.error("An error occurred:", error); + return ; + } + return +}; + const App = () => { const auth = getAuth(); + const [loggedInUser, setLoggedInUser] = React.useState(null); React.useEffect(() => { const unsubscribe = onAuthStateChanged(auth, (user) => { console.log("Auth State Triggered"); if (user) { - console.log(`User Registered: ${user}`); + console.log(`User Registered: ${user.email}`); setLoggedInUser(user); } else { setLoggedInUser(null); @@ -29,7 +46,7 @@ const App = () => { return () => { unsubscribe(); }; - }, []); + }); return ( @@ -42,7 +59,7 @@ const App = () => { path="/" element={auth.currentUser ? : } /> - } /> + ; } /> } /> diff --git a/src/components/ErrorPage.js b/src/components/ErrorPage.js new file mode 100644 index 00000000..f485a5e1 --- /dev/null +++ b/src/components/ErrorPage.js @@ -0,0 +1,49 @@ +import React from 'react'; +import { Box, Typography, Button, Icon } from '@mui/material'; +import { ArrowBack } from '@mui/icons-material'; +import {useNavigate} from "react-router-dom"; +import ErrorIcon from '@mui/icons-material/Error'; + +const ErrorPage = () => { + const errorCode = "401"; + const errorMessage = "Unauthorised User!" + const navigate = useNavigate(); + const onBackButtonClick = () => { + // Perform any necessary actions before navigation + // ... + // Navigate back to the previous page + navigate(-1); + }; + + return ( + + + + + + + Error {errorCode} + + + {errorMessage} + + + + ); +}; + +export default ErrorPage; \ No newline at end of file From daf14a9a8a1e20e2f81d8818f28437648a8b87b4 Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Tue, 18 Jul 2023 02:59:24 +0800 Subject: [PATCH 27/35] Added ADMIN USER --- src/components/AdminUpload.js | 8 +- src/components/Home.js | 11 +- src/components/ImageCard.js | 224 ----------------------------- src/components/ImageTile.js | 2 +- src/components/ResponsiveAppBar.js | 4 +- src/components/SearchBar.js | 2 +- src/components/SignOutButton.js | 4 +- src/components/SignUpAccount.js | 6 +- 8 files changed, 19 insertions(+), 242 deletions(-) delete mode 100644 src/components/ImageCard.js diff --git a/src/components/AdminUpload.js b/src/components/AdminUpload.js index 7fae8a7c..228d29a8 100644 --- a/src/components/AdminUpload.js +++ b/src/components/AdminUpload.js @@ -117,7 +117,7 @@ function AdminUpload(props) { }) .catch((error) => { // Handle upload error - console.log("Error uploading data:", error); + // console.log("Error uploading data:", error); }); } }); @@ -128,13 +128,13 @@ function AdminUpload(props) { setPass(""); //Reset the form setFiles([]); //Reset the form - console.log("Form submitted successfully"); - console.log("Form submitted with files:", files); + // console.log("Form submitted successfully"); + // console.log("Form submitted with files:", files); // Additional submission logic } else { setOpenErrorSnackbar(true); setOpenSuccessSnackbar(false); - console.log("Form is not valid. Please fill in all fields."); + // console.log("Form is not valid. Please fill in all fields."); } }; diff --git a/src/components/Home.js b/src/components/Home.js index 1aef1c9b..65d9e467 100644 --- a/src/components/Home.js +++ b/src/components/Home.js @@ -4,6 +4,7 @@ import SearchBar from "./SearchBar"; import ImgDownload from "./ImgDownload"; import { useAuth } from "./Auth"; + //Firebase - Pull data from server import { onChildAdded, ref as databaseRef } from "firebase/database"; import { database } from "../firebase"; @@ -73,7 +74,7 @@ const Home = () => { //Function that filters data based on input const filterData = (searchArray) => { let searchList = []; - console.log(searchArray); //["mountain","purple"] + // console.log(searchArray); //["mountain","purple"] //Email Filtering let emailFilter = imageObjects.filter( (obj) => obj.email === currentUser.email @@ -93,11 +94,11 @@ const Home = () => { return item.tagsarray.some((tags) => tags.label === element); }); - console.log(`Filtered Data: ${JSON.stringify(filteredData)}`); + // console.log(`Filtered Data: ${JSON.stringify(filteredData)}`); searchList.push(...filteredData); // return filteredData; } - console.log(`Final-List: ${JSON.stringify(searchList)}`); + // console.log(`Final-List: ${JSON.stringify(searchList)}`); const uniqueOutput = Object.values( searchList.reduce((acc, obj) => { acc[obj.key] = obj; @@ -113,9 +114,9 @@ const Home = () => { return (
    - {console.log(`Search Terms: ${filterTerms}`)} + {/* {console.log(`Search Terms: ${filterTerms}`)} {console.log(`Image Objects: ${JSON.stringify(imageObjects)}`)} - {console.log(`input: ${JSON.stringify(filterData(filterTerms))}`)} + {console.log(`input: ${JSON.stringify(filterData(filterTerms))}`)} */}
    diff --git a/src/components/ImageCard.js b/src/components/ImageCard.js deleted file mode 100644 index a91f6c5f..00000000 --- a/src/components/ImageCard.js +++ /dev/null @@ -1,224 +0,0 @@ -import React from "react"; -import ImageListItem from "@mui/material/ImageListItem"; -import { styled } from "@mui/material/styles"; -import Chip from "@mui/material/Chip"; -import Box from "@mui/material/Box"; -import { database } from "../firebase"; -import { ref as databaseRef, update } from "firebase/database"; -import TextField from "@mui/material/TextField"; - -const ListItem = styled("li")(({ theme }) => ({ - margin: theme.spacing(0.5), -})); - -export default function ImageTile(props) { - //Function: Takes in the image props and display them - const [chipData, setChipData] = React.useState([]); //Initial empty array - const [showInput, setShowInput] = React.useState(false); - const [inputValue, setInputValue] = React.useState(""); - const inputRef = React.useRef(null); - const imgRef = React.useRef(null); - - // 1. Function to set state on the files upon dropping - const IMAGEOBJECT_FOLDER_NAME = "imageObjects"; - - const handleInputChange = (event) => { - setInputValue(event.target.value); //Updating the texts of the component - }; - - const handleImageClick = () => { - console.log(`Image clicked: ${props.item.key}`); - setShowInput(!showInput); - }; - - const handleDelete = (chipToDelete) => () => { - console.log(chipToDelete); - console.log(`Deleted From: ${JSON.stringify(props.item)}`); - setChipData((chips) => - chips.filter((chip) => chip.key !== chipToDelete.key) - ); - }; - - const handleClickOutside = (event) => { - if ( - imgRef.current && - !imgRef.current.contains(event.target) && - inputRef.current && - !inputRef.current.contains(event.target) - ) { - console.log("clickedout"); - console.log(showInput); - setShowInput(false); - setInputValue(""); //reset the input - } - }; - - //This function updates the new object array for the chip - const addChipFormat = (chipValue) => { - let arrayData = [...chipData]; //copy value - chipValue = chipValue.replace(/\s/g, ""); - console.log(`Array Chip Length: ${arrayData.length}`); - if (arrayData.length !== 0) { - //if not empty - let lastKeyValue = arrayData[arrayData.length - 1].key; - let objectAppend = { - key: lastKeyValue + 1, //running number - label: chipValue.toLowerCase(), - }; - arrayData.push(objectAppend); //appends to the object array - console.log(arrayData); - return arrayData; - } else { - return [ - { - key: 1, - label: chipValue.toLowerCase(), - }, - ]; - } - }; - - //This handles the keyboard enter key to register submission - const handleKeyPress = (event) => { - if (event.key === "Enter") { - // Perform any necessary logic here - console.log(inputValue); - if (inputValue !== "") { - setChipData(addChipFormat(inputValue)); //append to the chip data - //Writing data into the database - const objectPath = IMAGEOBJECT_FOLDER_NAME + "/" + props.item.key; - const postListRef = databaseRef(database, objectPath); - console.log(`Path: ${objectPath}`); - console.log(`postListRef: ${postListRef}`); - // Update the parameter to firebase - update(postListRef, { tagsarray: addChipFormat(inputValue) }) - .then(() => { - console.log("Chips updated successfully"); - }) - .catch((error) => { - console.error("Error updating Chips:", error); - }); - - setShowInput(false); - setInputValue(""); //reset the input - } else { - } - } - }; - - React.useEffect(() => { - document.addEventListener("click", handleClickOutside); - return () => { - document.removeEventListener("click", handleClickOutside); - }; - }); - - React.useEffect(() => { - //whenever the props.item.tagsarray changes, it will update state - setChipData(props.item.tagsarray); - }, [props.item.tagsarray]); - - React.useEffect(() => { - // console.log(`chipData: ${chipData}`); - // Writing data into the database - const objectPath = `${IMAGEOBJECT_FOLDER_NAME}/${props.item.key}`; - const postListRef = databaseRef(database, objectPath); - // console.log(`Path: ${objectPath}`); - // console.log(`postListRef: ${postListRef}`); - // Update the parameter to Firebase - // Whenever chipdata changes it updates server - update(postListRef, { tagsarray: chipData }) - .then(() => { - console.log("Chips updated successfully"); - }) - .catch((error) => { - console.error("Error updating Chips:", error); - }); - }, [chipData, props.item.key]); - - //function to actually setup the sizes and image details for the tiling - function srcset(image, size, rows = 1, cols = 1) { - return { - src: `${image}?w=${size * cols}&h=${size * rows}&fit=crop&auto=format`, - srcSet: `${image}?w=${size * cols}&h=${ - size * rows - }&fit=crop&auto=format&dpr=2 2x`, - }; - } - - return ( - - {/* the number like 720 in changes the img quality */} - {props.item.title} - {showInput && ( -
    - -
    - )} - - - {props.item.tagsarray !== null - ? chipData.map((data) => { - //this data will be replaced by component tagging - console.log(`Data Received: ${JSON.stringify(props.item)}`); - console.log(`Chip Data: ${JSON.stringify(data)}`); - return ( - - {data.label !== "default" && ( //hide default chip label but retain it - - )} - - ); - }) - : null} - -
    - ); -} diff --git a/src/components/ImageTile.js b/src/components/ImageTile.js index e892593e..7b329b1e 100644 --- a/src/components/ImageTile.js +++ b/src/components/ImageTile.js @@ -5,7 +5,7 @@ import ImageCard from './ImageCard'; export default function ImageTile(props) { //Function: Takes in the image props and display them - console.log("Image Tile Rendered") + // console.log("Image Tile Rendered") return (
    diff --git a/src/components/ResponsiveAppBar.js b/src/components/ResponsiveAppBar.js index 768e526a..08281d7c 100644 --- a/src/components/ResponsiveAppBar.js +++ b/src/components/ResponsiveAppBar.js @@ -39,8 +39,8 @@ function ResponsiveAppBar() { return ( - {console.log(JSON.stringify(currentUser))} - {console.log(currentUser?.photoURL)} + {/* {console.log(JSON.stringify(currentUser))} + {console.log(currentUser?.photoURL)} */} diff --git a/src/components/SearchBar.js b/src/components/SearchBar.js index 06a280a0..b2709229 100644 --- a/src/components/SearchBar.js +++ b/src/components/SearchBar.js @@ -10,7 +10,7 @@ export default function SearchBar({ onSearch }) { }; const handleSearch = () => { - console.log(`Searching Keyword: ${searchTerm}`); + // console.log(`Searching Keyword: ${searchTerm}`); onSearch(searchTerm); }; diff --git a/src/components/SignOutButton.js b/src/components/SignOutButton.js index ba6114ef..689bee10 100644 --- a/src/components/SignOutButton.js +++ b/src/components/SignOutButton.js @@ -8,11 +8,11 @@ const SignOutButton = () => { signOut(auth) .then(() => { // Sign-out successful. - console.log("User signed out successfully"); + // console.log("User signed out successfully"); }) .catch((error) => { // An error happened. - console.error("Error occurred while signing out:", error); + // console.error("Error occurred while signing out:", error); }); }; diff --git a/src/components/SignUpAccount.js b/src/components/SignUpAccount.js index bf4ed85c..03a7c633 100644 --- a/src/components/SignUpAccount.js +++ b/src/components/SignUpAccount.js @@ -46,7 +46,7 @@ export default function SignUp() { useEffect(() => { if (currentUser) { - console.log("Welcome " + currentUser.email); + // console.log("Welcome " + currentUser.email); } }, [currentUser, navigate]); @@ -67,7 +67,7 @@ export default function SignUp() { const user = userCredential.user; // Updating user name await updateProfile(auth.currentUser, { displayName: "Default" }); - console.log(user); + // console.log(user); navigate("/"); // ... }) @@ -75,7 +75,7 @@ export default function SignUp() { const errorCode = error.code; const errorMessage = error.message; setError(errorMessage); - console.log(errorCode, errorMessage); + // console.log(errorCode, errorMessage); // .. }); }; From 15944e56ad4c0784e6e0533c543a02d502d24144 Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Tue, 18 Jul 2023 03:18:58 +0800 Subject: [PATCH 28/35] Removed console.logs. Downsized images to 180 --- src/components/Home.js | 2 +- src/components/ImageCard.js | 224 ++++++++++++++++++++++++++++++++++++ 2 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 src/components/ImageCard.js diff --git a/src/components/Home.js b/src/components/Home.js index 65d9e467..4eb196cf 100644 --- a/src/components/Home.js +++ b/src/components/Home.js @@ -28,7 +28,7 @@ const Home = () => { // This code will run when the component unmounts // You can perform any necessary cleanup here onChildAdded(imgListRef, (data) => { - console.log(`Server Data: ${JSON.stringify(data)}`); + // console.log(`Server Data: ${JSON.stringify(data)}`); // Add the subsequent child to local component state, initialising a new array to trigger re-render //console.log(data.val().imgurl) setImageObjects( diff --git a/src/components/ImageCard.js b/src/components/ImageCard.js new file mode 100644 index 00000000..73a56dba --- /dev/null +++ b/src/components/ImageCard.js @@ -0,0 +1,224 @@ +import React from "react"; +import ImageListItem from "@mui/material/ImageListItem"; +import { styled } from "@mui/material/styles"; +import Chip from "@mui/material/Chip"; +import Box from "@mui/material/Box"; +import { database } from "../firebase"; +import { ref as databaseRef, update } from "firebase/database"; +import TextField from "@mui/material/TextField"; + +const ListItem = styled("li")(({ theme }) => ({ + margin: theme.spacing(0.5), +})); + +export default function ImageTile(props) { + //Function: Takes in the image props and display them + const [chipData, setChipData] = React.useState([]); //Initial empty array + const [showInput, setShowInput] = React.useState(false); + const [inputValue, setInputValue] = React.useState(""); + const inputRef = React.useRef(null); + const imgRef = React.useRef(null); + + // 1. Function to set state on the files upon dropping + const IMAGEOBJECT_FOLDER_NAME = "imageObjects"; + + const handleInputChange = (event) => { + setInputValue(event.target.value); //Updating the texts of the component + }; + + const handleImageClick = () => { + // console.log(`Image clicked: ${props.item.key}`); + setShowInput(!showInput); + }; + + const handleDelete = (chipToDelete) => () => { + // console.log(chipToDelete); + // console.log(`Deleted From: ${JSON.stringify(props.item)}`) + setChipData((chips) => + chips.filter((chip) => chip.key !== chipToDelete.key) + ); + }; + + const handleClickOutside = (event) => { + if ( + imgRef.current && + !imgRef.current.contains(event.target) && + inputRef.current && + !inputRef.current.contains(event.target) + ) { + // console.log("clickedout"); + // console.log(showInput); + setShowInput(false); + setInputValue(""); //reset the input + } + }; + + //This function updates the new object array for the chip + const addChipFormat = (chipValue) => { + let arrayData = [...chipData]; //copy value + chipValue = chipValue.replace(/\s/g, ""); + // console.log(`Array Chip Length: ${arrayData.length}`); + if (arrayData.length !== 0) { + //if not empty + let lastKeyValue = arrayData[arrayData.length - 1].key; + let objectAppend = { + key: lastKeyValue + 1, //running number + label: chipValue.toLowerCase(), + }; + arrayData.push(objectAppend); //appends to the object array + // console.log(arrayData); + return arrayData; + } else { + return [ + { + key: 1, + label: chipValue.toLowerCase(), + }, + ]; + } + }; + + //This handles the keyboard enter key to register submission + const handleKeyPress = (event) => { + if (event.key === "Enter") { + // Perform any necessary logic here + // console.log(inputValue); + if (inputValue !== "") { + setChipData(addChipFormat(inputValue)); //append to the chip data + //Writing data into the database + const objectPath = IMAGEOBJECT_FOLDER_NAME + "/" + props.item.key; + const postListRef = databaseRef(database, objectPath); + // console.log(`Path: ${objectPath}`); + // console.log(`postListRef: ${postListRef}`); + // Update the parameter to firebase + update(postListRef, { tagsarray: addChipFormat(inputValue) }) + .then(() => { + // console.log("Chips updated successfully"); + }) + .catch((error) => { + // console.error("Error updating Chips:", error); + }); + + setShowInput(false); + setInputValue(""); //reset the input + } else { + } + } + }; + + React.useEffect(() => { + document.addEventListener("click", handleClickOutside); + return () => { + document.removeEventListener("click", handleClickOutside); + }; + }); + + React.useEffect(() => { + //whenever the props.item.tagsarray changes, it will update state + setChipData(props.item.tagsarray); + }, [props.item.tagsarray]); + + React.useEffect(() => { + // console.log(`chipData: ${chipData}`); + // Writing data into the database + const objectPath = `${IMAGEOBJECT_FOLDER_NAME}/${props.item.key}`; + const postListRef = databaseRef(database, objectPath); + // console.log(`Path: ${objectPath}`); + // console.log(`postListRef: ${postListRef}`); + // Update the parameter to Firebase + // Whenever chipdata changes it updates server + update(postListRef, { tagsarray: chipData }) + .then(() => { + // console.log("Chips updated successfully"); + }) + .catch((error) => { + // console.error("Error updating Chips:", error); + }); + }, [chipData,props.item.key]); + + //function to actually setup the sizes and image details for the tiling + function srcset(image, size, rows = 1, cols = 1) { + return { + src: `${image}?w=${size * cols}&h=${size * rows}&fit=crop&auto=format`, + srcSet: `${image}?w=${size * cols}&h=${ + size * rows + }&fit=crop&auto=format&dpr=2 2x`, + }; + } + + return ( + + {/* the number like 720 in changes the img quality */} + {props.item.title} + {showInput && ( +
    + +
    + )} + + + {props.item.tagsarray !== null + ? chipData.map((data) => { + //this data will be replaced by component tagging + // console.log(`Data Received: ${JSON.stringify(props.item)}`) + // console.log(`Chip Data: ${JSON.stringify(data)}`) + return ( + + {data.label !== "default" && ( //hide default chip label but retain it + + )} + + ); + }) + : null} + +
    + ); +} From fd43114d1732f777c6e90ff30cc076611ce9773f Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Tue, 18 Jul 2023 03:23:52 +0800 Subject: [PATCH 29/35] Removed more console.logs --- src/App.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/App.js b/src/App.js index 8e0d5689..c0b42da9 100644 --- a/src/App.js +++ b/src/App.js @@ -21,7 +21,7 @@ const PrivateAdminRoute = (user) => { } } catch (error) { // Code to handle the error - console.error("An error occurred:", error); + // console.error("An error occurred:", error); return ; } return @@ -34,9 +34,9 @@ const App = () => { React.useEffect(() => { const unsubscribe = onAuthStateChanged(auth, (user) => { - console.log("Auth State Triggered"); + // console.log("Auth State Triggered"); if (user) { - console.log(`User Registered: ${user.email}`); + // console.log(`User Registered: ${user.email}`); setLoggedInUser(user); } else { setLoggedInUser(null); From b211b57e3953546512db0bfa0d353e530dba8a16 Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Tue, 18 Jul 2023 03:45:53 +0800 Subject: [PATCH 30/35] Changed CI=false for railway deployment --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f10cbbf8..e7930422 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ }, "scripts": { "start": "react-scripts start", - "build": "react-scripts build" + "build": "CI=false react-scripts build", }, "eslintConfig": { "extends": [ From c95de68590f099e68f911a8218c335286ebfcf57 Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Tue, 18 Jul 2023 03:48:16 +0800 Subject: [PATCH 31/35] Recorrection of the CI build, to transfer to Railway App --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e7930422..969de481 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ }, "scripts": { "start": "react-scripts start", - "build": "CI=false react-scripts build", + "build": "react-scripts build", }, "eslintConfig": { "extends": [ From d5bdea83bdbe32b7c90749903348c4cd2c99728a Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Tue, 18 Jul 2023 03:50:04 +0800 Subject: [PATCH 32/35] Removed trailing comma in .json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 969de481..f10cbbf8 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ }, "scripts": { "start": "react-scripts start", - "build": "react-scripts build", + "build": "react-scripts build" }, "eslintConfig": { "extends": [ From 88ccb990e7a5dbf3bc4849476ad87963354994f0 Mon Sep 17 00:00:00 2001 From: dexterch91 Date: Sat, 22 Jul 2023 23:01:24 +0800 Subject: [PATCH 33/35] Cleaned up and organized for submission --- src/components/AdminUpload.js | 48 ++++++++----- src/components/Auth.js | 8 ++- src/components/ErrorPage.js | 41 ++++++----- src/components/Home.js | 12 ++-- src/components/ImageCard.js | 112 ++++++++++++++++------------- src/components/ImgDownload.js | 10 ++- src/components/LoginDefault.js | 26 ++++--- src/components/ResponsiveAppBar.js | 32 ++++++--- src/components/SearchBar.js | 2 +- src/components/SignUpAccount.js | 29 ++++---- 10 files changed, 188 insertions(+), 132 deletions(-) diff --git a/src/components/AdminUpload.js b/src/components/AdminUpload.js index 228d29a8..7e9ac100 100644 --- a/src/components/AdminUpload.js +++ b/src/components/AdminUpload.js @@ -1,5 +1,7 @@ import React, { useEffect, useState } from "react"; import { useDropzone } from "react-dropzone"; + +//MUI import { TextField, FormLabel, @@ -21,6 +23,10 @@ import { } from "firebase/storage"; import { database, storage } from "../firebase"; +//////////////////////////////// +//Component: AdminUpload - Admin upload page +//////////////////////////////// + function AdminUpload(props) { const [name, setName] = useState(""); const [email, setEmail] = useState(""); @@ -34,19 +40,36 @@ function AdminUpload(props) { const IMAGEOBJECT_FOLDER_NAME = "imageObjects"; const IMAGES_FOLDER_NAME = "images"; //Images folder name - //Function: Upload time + //////////////////////////////// + //F1. Upload time + //////////////////////////////// const uploadDateTime = () => { const uploadData = new Date(); const formattedDate = uploadData.toLocaleDateString(); const formattedTime = uploadData.toLocaleTimeString(); return "[" + formattedDate + " " + formattedTime + "]"; }; - // 2. Perform your form submission logic here, along with file upload handling + //////////////////////////////// + //F2. Validate form fields + //////////////////////////////// + const isFormValid = () => { + return ( + name.trim() !== "" && + email.trim() !== "" && + pass.trim() !== "" && + files.length !== 0 + ); + }; + + // 2. Perform your form submission logic here, along with file upload handling const Alert = React.forwardRef(function Alert(props, ref) { return ; }); + //////////////////////////////// + //1. On-Clicking/Validation Functions + //////////////////////////////// const handleCloseSuccessSnackbar = () => { setOpenSuccessSnackbar(false); }; @@ -138,18 +161,7 @@ function AdminUpload(props) { } }; - // Validate form fields - const isFormValid = () => { - return ( - name.trim() !== "" && - email.trim() !== "" && - pass.trim() !== "" && - files.length !== 0 - ); - }; - //handling value inputs and display - const handleNameChange = (event) => { setName(event.target.value); }; @@ -162,7 +174,9 @@ function AdminUpload(props) { setPass(event.target.value); }; - //2b. Password Generator + //////////////////////////////// + //2b. Random Password Generator + //////////////////////////////// function generateRandomPassword(length) { const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()"; @@ -182,7 +196,9 @@ function AdminUpload(props) { setPass(password); }; - // 3. Dropzone Params: Restricting Dropzone to only images files + //////////////////////////////// + //3. Dropzone Params: Restricting Dropzone to only images files + //////////////////////////////// const { getRootProps, getInputProps } = useDropzone({ accept: { "image/jpeg": [], @@ -216,7 +232,7 @@ function AdminUpload(props) { )); useEffect(() => { - // Make sure to revoke the data uris to avoid memory leaks, will run on unmount + // Make sure to revoke the data urls to avoid memory leaks, will run on unmount return () => files.forEach((file) => URL.revokeObjectURL(file.preview)); }, [files]); diff --git a/src/components/Auth.js b/src/components/Auth.js index d904c8ae..efe18fe8 100644 --- a/src/components/Auth.js +++ b/src/components/Auth.js @@ -7,12 +7,14 @@ import { signOut, } from "firebase/auth"; import { auth } from "../firebase"; -import React, { useEffect } from "react"; -import { useState } from "react"; -import { useContext, createContext } from "react"; +import React, { useEffect, useState, useContext, createContext } from "react"; const authContext = createContext(); +//////////////////////////////// +//Component: Auth: Google login authentication +//////////////////////////////// + // react hook to handle user authentication export const useAuth = () => { return useContext(authContext); diff --git a/src/components/ErrorPage.js b/src/components/ErrorPage.js index f485a5e1..32d6ca32 100644 --- a/src/components/ErrorPage.js +++ b/src/components/ErrorPage.js @@ -1,19 +1,22 @@ -import React from 'react'; -import { Box, Typography, Button, Icon } from '@mui/material'; -import { ArrowBack } from '@mui/icons-material'; -import {useNavigate} from "react-router-dom"; -import ErrorIcon from '@mui/icons-material/Error'; +import React from "react"; +import { Box, Typography, Button, Icon } from "@mui/material"; +import { ArrowBack } from "@mui/icons-material"; +import { useNavigate } from "react-router-dom"; +import ErrorIcon from "@mui/icons-material/Error"; +//////////////////////////////// +//Component: ErrorPage - Display error notification if admin not identified +//////////////////////////////// const ErrorPage = () => { - const errorCode = "401"; - const errorMessage = "Unauthorised User!" - const navigate = useNavigate(); - const onBackButtonClick = () => { - // Perform any necessary actions before navigation - // ... - // Navigate back to the previous page - navigate(-1); - }; + const errorCode = "401"; + const errorMessage = "Unauthorised User!"; + const navigate = useNavigate(); + const onBackButtonClick = () => { + // Perform any necessary actions before navigation + // ... + // Navigate back to the previous page + navigate(-1); + }; return ( { justifyContent="center" height="100vh" > - - - + + + Error {errorCode} @@ -38,7 +41,7 @@ const ErrorPage = () => { color="primary" startIcon={} onClick={onBackButtonClick} - sx={{ margin: '16px auto' }} + sx={{ margin: "16px auto" }} > Back @@ -46,4 +49,4 @@ const ErrorPage = () => { ); }; -export default ErrorPage; \ No newline at end of file +export default ErrorPage; diff --git a/src/components/Home.js b/src/components/Home.js index 4eb196cf..684b5e1f 100644 --- a/src/components/Home.js +++ b/src/components/Home.js @@ -4,29 +4,28 @@ import SearchBar from "./SearchBar"; import ImgDownload from "./ImgDownload"; import { useAuth } from "./Auth"; - //Firebase - Pull data from server import { onChildAdded, ref as databaseRef } from "firebase/database"; import { database } from "../firebase"; +// Constant for db_name const IMAGEOBJECT_FOLDER_NAME = "imageObjects"; +//////////////////////////////// +//Component: Home - Displaying Tiled Images +//////////////////////////////// const Home = () => { const [imageObjects, setImageObjects] = useState([]); //State 1 const [filterTerms, setfilterTerms] = useState([]); const { currentUser } = useAuth(); useEffect(() => { - // This effect will run when the component mounts and whenever the 'yourCollection' data changes in Firebase. - // You can perform any necessary operations here, such as updating state, manipulating the data, etc. - // Make sure to handle any cleanup if required (return a cleanup function). const imgListRef = databaseRef(database, IMAGEOBJECT_FOLDER_NAME); // Subscribe to the Firebase listener //console.log(imgListRef); return () => { // This code will run when the component unmounts - // You can perform any necessary cleanup here onChildAdded(imgListRef, (data) => { // console.log(`Server Data: ${JSON.stringify(data)}`); // Add the subsequent child to local component state, initialising a new array to trigger re-render @@ -61,6 +60,9 @@ const Home = () => { // console.log(imageObjects) // extracting the key and the image only for downloading + //////////////////////////////// + //Functions + //////////////////////////////// const updateTerms = (searchTerm) => { const keywords = searchTerm.toLowerCase().split(" "); //split by spaces // setImageObjects([]); //reset data when search is clicked diff --git a/src/components/ImageCard.js b/src/components/ImageCard.js index 73a56dba..7f73325c 100644 --- a/src/components/ImageCard.js +++ b/src/components/ImageCard.js @@ -1,16 +1,18 @@ import React from "react"; -import ImageListItem from "@mui/material/ImageListItem"; -import { styled } from "@mui/material/styles"; +import { ImageListItem, styled, TextField } from "@mui/material/"; import Chip from "@mui/material/Chip"; import Box from "@mui/material/Box"; import { database } from "../firebase"; import { ref as databaseRef, update } from "firebase/database"; -import TextField from "@mui/material/TextField"; const ListItem = styled("li")(({ theme }) => ({ margin: theme.spacing(0.5), })); +//////////////////////////////// +//Component: ImageTile - Display Image object +//////////////////////////////// + export default function ImageTile(props) { //Function: Takes in the image props and display them const [chipData, setChipData] = React.useState([]); //Initial empty array @@ -19,9 +21,13 @@ export default function ImageTile(props) { const inputRef = React.useRef(null); const imgRef = React.useRef(null); - // 1. Function to set state on the files upon dropping + // Constant for db_name const IMAGEOBJECT_FOLDER_NAME = "imageObjects"; + //////////////////////////////// + //1. On-Clicking/Validation Functions + //////////////////////////////// + const handleInputChange = (event) => { setInputValue(event.target.value); //Updating the texts of the component }; @@ -53,31 +59,6 @@ export default function ImageTile(props) { } }; - //This function updates the new object array for the chip - const addChipFormat = (chipValue) => { - let arrayData = [...chipData]; //copy value - chipValue = chipValue.replace(/\s/g, ""); - // console.log(`Array Chip Length: ${arrayData.length}`); - if (arrayData.length !== 0) { - //if not empty - let lastKeyValue = arrayData[arrayData.length - 1].key; - let objectAppend = { - key: lastKeyValue + 1, //running number - label: chipValue.toLowerCase(), - }; - arrayData.push(objectAppend); //appends to the object array - // console.log(arrayData); - return arrayData; - } else { - return [ - { - key: 1, - label: chipValue.toLowerCase(), - }, - ]; - } - }; - //This handles the keyboard enter key to register submission const handleKeyPress = (event) => { if (event.key === "Enter") { @@ -106,7 +87,11 @@ export default function ImageTile(props) { } }; + //////////////////////////////// + //2. useEffects + //////////////////////////////// React.useEffect(() => { + //To listen when user clicks outside field document.addEventListener("click", handleClickOutside); return () => { document.removeEventListener("click", handleClickOutside); @@ -119,8 +104,8 @@ export default function ImageTile(props) { }, [props.item.tagsarray]); React.useEffect(() => { - // console.log(`chipData: ${chipData}`); // Writing data into the database + // console.log(`chipData: ${chipData}`); const objectPath = `${IMAGEOBJECT_FOLDER_NAME}/${props.item.key}`; const postListRef = databaseRef(database, objectPath); // console.log(`Path: ${objectPath}`); @@ -134,7 +119,11 @@ export default function ImageTile(props) { .catch((error) => { // console.error("Error updating Chips:", error); }); - }, [chipData,props.item.key]); + }, [chipData, props.item.key]); + + //////////////////////////////// + //Functions + //////////////////////////////// //function to actually setup the sizes and image details for the tiling function srcset(image, size, rows = 1, cols = 1) { @@ -146,6 +135,31 @@ export default function ImageTile(props) { }; } + //This function updates the new object array for the chip + const addChipFormat = (chipValue) => { + let arrayData = [...chipData]; //copy value + chipValue = chipValue.replace(/\s/g, ""); + // console.log(`Array Chip Length: ${arrayData.length}`); + if (arrayData.length !== 0) { + //if not empty + let lastKeyValue = arrayData[arrayData.length - 1].key; + let objectAppend = { + key: lastKeyValue + 1, //running number + label: chipValue.toLowerCase(), + }; + arrayData.push(objectAppend); //appends to the object array + // console.log(arrayData); + return arrayData; + } else { + return [ + { + key: 1, + label: chipValue.toLowerCase(), + }, + ]; + } + }; + return ( {showInput && ( -
    - + -
    + onChange={handleInputChange} + inputProps={{ maxLength: 8, autoFocus: true }} + hiddenLabel + id="filled-hidden-label-small" + defaultValue="Small" + variant="filled" + size="small" + onKeyPress={handleKeyPress} + /> +
    )} { - const [imgUrl, setimgUrl] = useState(null); + // const [imgUrl, setimgUrl] = useState(null); const downloadFilesAsZip = async (fileUrls) => { const zip = new JSZip(); @@ -48,8 +46,8 @@ const ImgDownload = (props) => { //map into an arrage of img links for download const objectList = props.ImageObjects; const urlArray = objectList.map(({ imgurl }) => imgurl); - console.log(urlArray); - setimgUrl(urlArray); //set the state for downloads + // console.log(urlArray); + // setimgUrl(urlArray); //set the state for downloads downloadFilesAsZip(urlArray); }; diff --git a/src/components/LoginDefault.js b/src/components/LoginDefault.js index 0eb5358a..9528694a 100644 --- a/src/components/LoginDefault.js +++ b/src/components/LoginDefault.js @@ -1,18 +1,24 @@ import React, { useState, useEffect } from "react"; -import Avatar from "@mui/material/Avatar"; -import CssBaseline from "@mui/material/CssBaseline"; -import Link from "@mui/material/Link"; -import Grid from "@mui/material/Grid"; -import Box from "@mui/material/Box"; -import LockOutlinedIcon from "@mui/icons-material/LockOutlined"; +import { + Container, + Typography, + TextField, + Button, + Link, + Grid, + Box, + Avatar, + CssBaseline, +} from "@mui/material"; + import { createTheme, ThemeProvider } from "@mui/material/styles"; -import { Container, Typography, TextField, Button } from "@mui/material"; -import { useAuth } from "./Auth"; import { getAuth, sendPasswordResetEmail } from "firebase/auth"; -import { useNavigate } from "react-router-dom"; import GoogleIcon from "@mui/icons-material/Google"; +import LockOutlinedIcon from "@mui/icons-material/LockOutlined"; import Alert from "@mui/material/Alert"; -import { set } from "firebase/database"; + +import { useAuth } from "./Auth"; +import { useNavigate } from "react-router-dom"; function Copyright(props) { return ( diff --git a/src/components/ResponsiveAppBar.js b/src/components/ResponsiveAppBar.js index 08281d7c..e045e71d 100644 --- a/src/components/ResponsiveAppBar.js +++ b/src/components/ResponsiveAppBar.js @@ -1,27 +1,37 @@ import * as React from "react"; -import AppBar from "@mui/material/AppBar"; -import Box from "@mui/material/Box"; -import Toolbar from "@mui/material/Toolbar"; -import IconButton from "@mui/material/IconButton"; -import Typography from "@mui/material/Typography"; -import Menu from "@mui/material/Menu"; +import { + AppBar, + Box, + Toolbar, + IconButton, + Typography, + Menu, + MenuItem, + Container, + Avatar, + Button, + Tooltip, +} from "@mui/material"; import MenuIcon from "@mui/icons-material/Menu"; -import Container from "@mui/material/Container"; -import Avatar from "@mui/material/Avatar"; -import Button from "@mui/material/Button"; -import Tooltip from "@mui/material/Tooltip"; -import MenuItem from "@mui/material/MenuItem"; import CameraIcon from "@mui/icons-material/Camera"; import { useAuth } from "./Auth"; import SignOutButton from "./SignOutButton"; +//Shortcut to display buttons on the appbar const pages = ["About", "Contact Us"]; +//////////////////////////////// +//Component: ResponsiveAppBar - Application Navbar +//////////////////////////////// + function ResponsiveAppBar() { const { currentUser } = useAuth(); const [anchorElNav, setAnchorElNav] = React.useState(null); const [anchorElUser, setAnchorElUser] = React.useState(null); + //////////////////////////////// + //1. On-Clicking/Validation Functions + //////////////////////////////// const handleOpenNavMenu = (event) => { setAnchorElNav(event.currentTarget); }; diff --git a/src/components/SearchBar.js b/src/components/SearchBar.js index b2709229..47e9803e 100644 --- a/src/components/SearchBar.js +++ b/src/components/SearchBar.js @@ -1,6 +1,6 @@ -import { Button, Container, InputAdornment, TextField } from "@mui/material"; import { useState } from "react"; import SearchIcon from "@mui/icons-material/Search"; +import { Button, Container, InputAdornment, TextField } from "@mui/material"; export default function SearchBar({ onSearch }) { const [searchTerm, setSearchTerm] = useState(""); diff --git a/src/components/SignUpAccount.js b/src/components/SignUpAccount.js index 03a7c633..befd8477 100644 --- a/src/components/SignUpAccount.js +++ b/src/components/SignUpAccount.js @@ -1,19 +1,24 @@ import React, { useState, useEffect } from "react"; +import { + Container, + Typography, + TextField, + Button, + Link, + Grid, + Box, +} from "@mui/material"; +import Alert from "@mui/material/Alert"; import Avatar from "@mui/material/Avatar"; import CssBaseline from "@mui/material/CssBaseline"; -import FormControlLabel from "@mui/material/FormControlLabel"; -import Checkbox from "@mui/material/Checkbox"; -import Link from "@mui/material/Link"; -import Grid from "@mui/material/Grid"; -import Box from "@mui/material/Box"; -import LockOutlinedIcon from "@mui/icons-material/LockOutlined"; import { createTheme, ThemeProvider } from "@mui/material/styles"; -import { Container, Typography, TextField, Button } from "@mui/material"; -import { useAuth } from "./Auth"; -import { useNavigate } from "react-router-dom"; +import LockOutlinedIcon from "@mui/icons-material/LockOutlined"; + import { createUserWithEmailAndPassword, updateProfile } from "firebase/auth"; import { auth } from "../firebase"; -import Alert from "@mui/material/Alert"; + +import { useAuth } from "./Auth"; +import { useNavigate } from "react-router-dom"; function Copyright(props) { return ( @@ -64,7 +69,7 @@ export default function SignUp() { await createUserWithEmailAndPassword(auth, email, password) .then(async (userCredential) => { // Signed in - const user = userCredential.user; + // const user = userCredential.user; // Updating user name await updateProfile(auth.currentUser, { displayName: "Default" }); // console.log(user); @@ -72,7 +77,7 @@ export default function SignUp() { // ... }) .catch((error) => { - const errorCode = error.code; + // const errorCode = error.code; const errorMessage = error.message; setError(errorMessage); // console.log(errorCode, errorMessage); From 66ca54d260aee37c3c5c91223b73b8f3afbdb199 Mon Sep 17 00:00:00 2001 From: Dexter Chew <62207671+dexterch91@users.noreply.github.com> Date: Sun, 23 Jul 2023 13:39:40 +0800 Subject: [PATCH 34/35] Update README.md Added Video Preview --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 99c37870..b57ca618 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,9 @@ An image gallery for photographers to upload pictures online and assign them to ![Screenshot 2023-07-15 at 10-35-05 Pixfolio App](https://github.com/dexterch91/project2-bootcamp/assets/38061057/4ce71dcc-073d-4066-a029-ef7a802982cd) +## Demo Video +![WebApp__Pixfolio_ReactFirebaseOAuthGoogleAuth](https://github.com/dexterch91/project2-bootcamp/assets/62207671/fccf0568-4ea5-4121-a6b9-15042e73aa76) + # Features Upload screen which allows the admin to upload multiples images and tag it to users. From 5ac396de020cdecd6ff7c2906c2b71c5a74fc1d9 Mon Sep 17 00:00:00 2001 From: Dexter Chew <62207671+dexterch91@users.noreply.github.com> Date: Sun, 23 Jul 2023 13:47:19 +0800 Subject: [PATCH 35/35] Update README.md Upgrade gif. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b57ca618..a0fa9cec 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ An image gallery for photographers to upload pictures online and assign them to ![Screenshot 2023-07-15 at 10-35-05 Pixfolio App](https://github.com/dexterch91/project2-bootcamp/assets/38061057/4ce71dcc-073d-4066-a029-ef7a802982cd) ## Demo Video -![WebApp__Pixfolio_ReactFirebaseOAuthGoogleAuth](https://github.com/dexterch91/project2-bootcamp/assets/62207671/fccf0568-4ea5-4121-a6b9-15042e73aa76) +![WebApp__Pixfolio_ReactFirebaseOAuthGoogleAuth (1)](https://github.com/dexterch91/project2-bootcamp/assets/62207671/176ac24e-ebb8-44f1-9054-7f79b1e9ac22) # Features