From b488d751bfa9642c14c5e781395bebc42ab47e21 Mon Sep 17 00:00:00 2001 From: plexin123 <67119125+plexin123@users.noreply.github.com> Date: Thu, 21 Mar 2024 17:22:51 -0700 Subject: [PATCH 01/28] Create ImageStorage add image Storage file --- package-lock.json | 345 +++++++++++++++++++++++++++++- package.json | 4 + server/index.js | 11 +- server/package-lock.json | 14 ++ server/package.json | 1 + server/src/articles/controller.js | 1 - server/src/articles/routes.js | 3 + server/src/images/config.js | 13 ++ server/src/images/controller.js | 103 +++++++++ server/src/images/multer.js | 12 ++ server/src/images/routes.js | 11 + 11 files changed, 510 insertions(+), 8 deletions(-) create mode 100644 server/src/images/config.js create mode 100644 server/src/images/controller.js create mode 100644 server/src/images/multer.js create mode 100644 server/src/images/routes.js diff --git a/package-lock.json b/package-lock.json index a0dd4dd..a593783 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,13 @@ "license": "ISC", "dependencies": { "@vitejs/plugin-react": "^4.2.1", + "cloudinary": "^2.0.3", "cors": "^2.8.5", + "dotenv": "^16.4.5", "express": "^4.18.2", "jsonwebtoken": "^9.0.2", + "modules": "^0.4.0", + "multer": "^1.4.5-lts.1", "man": "^2.0.0", "vite": "^5.0.12" }, @@ -1159,6 +1163,11 @@ "node": ">= 8" } }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -1273,6 +1282,22 @@ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1382,6 +1407,18 @@ "node": ">=12" } }, + "node_modules/cloudinary": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-2.0.3.tgz", + "integrity": "sha512-2JPxAUuV4iHwiW4ATSOZvii6+BhhKI9+9KscgUkxJPKa6V6wOnZJHlYyovBGrrIbIgEdmGSZgqEsLfD0wWBhBg==", + "dependencies": { + "lodash": "^4.17.21", + "q": "^1.5.1" + }, + "engines": { + "node": ">=9" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1406,6 +1443,20 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, "node_modules/concurrently": { "version": "8.2.2", "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", @@ -1470,6 +1521,11 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -1549,6 +1605,17 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -1997,6 +2064,11 @@ "node": ">=0.12.0" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2067,8 +2139,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.includes": { "version": "4.3.0", @@ -2203,11 +2274,55 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/modules": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/modules/-/modules-0.4.0.tgz", + "integrity": "sha512-LX4JgwPHJr1FurPDKp1BlGgMXqZXtxO1O8ABGmj2g15CbLGlInTHcA9flqw6uN6oYKE2T0ngWdiHvcX97mdBsw==", + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/multer": { + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -2395,6 +2510,11 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -2413,6 +2533,15 @@ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", "dev": true }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -2516,6 +2645,25 @@ "react-dom": ">=16.8" } }, + "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/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -2773,6 +2921,27 @@ "node": ">= 0.8" } }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "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/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -2881,6 +3050,11 @@ "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -2924,6 +3098,11 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -3011,6 +3190,14 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -3756,6 +3943,11 @@ "picomatch": "^2.0.4" } }, + "append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -3842,6 +4034,19 @@ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "requires": { + "streamsearch": "^1.1.0" + } + }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -3910,6 +4115,15 @@ "wrap-ansi": "^7.0.0" } }, + "cloudinary": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-2.0.3.tgz", + "integrity": "sha512-2JPxAUuV4iHwiW4ATSOZvii6+BhhKI9+9KscgUkxJPKa6V6wOnZJHlYyovBGrrIbIgEdmGSZgqEsLfD0wWBhBg==", + "requires": { + "lodash": "^4.17.21", + "q": "^1.5.1" + } + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3931,6 +4145,17 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, "concurrently": { "version": "8.2.2", "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", @@ -3976,6 +4201,11 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, "cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -4029,6 +4259,11 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, + "dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==" + }, "ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -4374,6 +4609,11 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "devOptional": true }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4428,8 +4668,7 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.includes": { "version": "4.3.0", @@ -4531,11 +4770,43 @@ "brace-expansion": "^1.1.7" } }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "requires": { + "minimist": "^1.2.6" + } + }, + "modules": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/modules/-/modules-0.4.0.tgz", + "integrity": "sha512-LX4JgwPHJr1FurPDKp1BlGgMXqZXtxO1O8ABGmj2g15CbLGlInTHcA9flqw6uN6oYKE2T0ngWdiHvcX97mdBsw==" + }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "multer": { + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "requires": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + } + }, "nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -4650,6 +4921,11 @@ "source-map-js": "^1.0.2" } }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -4665,6 +4941,11 @@ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", "dev": true }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==" + }, "qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -4734,6 +5015,27 @@ "react-router": "6.18.0" } }, + "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" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -4930,6 +5232,26 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, + "streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==" + }, + "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" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -5008,6 +5330,11 @@ "mime-types": "~2.1.24" } }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, "undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -5028,6 +5355,11 @@ "picocolors": "^1.0.0" } }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -5060,6 +5392,11 @@ "strip-ansi": "^6.0.0" } }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 78768df..025f23e 100644 --- a/package.json +++ b/package.json @@ -28,9 +28,13 @@ }, "dependencies": { "@vitejs/plugin-react": "^4.2.1", + "cloudinary": "^2.0.3", "cors": "^2.8.5", + "dotenv": "^16.4.5", "express": "^4.18.2", "jsonwebtoken": "^9.0.2", + "modules": "^0.4.0", + "multer": "^1.4.5-lts.1", "man": "^2.0.0", "vite": "^5.0.12" } diff --git a/server/index.js b/server/index.js index ac79b21..7306422 100644 --- a/server/index.js +++ b/server/index.js @@ -3,15 +3,20 @@ const create_delete_articleRoutes = require("./src/articles/cdroutes.js"); const articleRoutes = require("./src/articles/routes.js"); const courseRoutes = require("./src/courses/routes.js"); const userRoutes = require("./src/users/routes.js"); +const imagesRoutes = require("./src/images/routes.js"); const cors = require("cors"); -require("dotenv").config(); +const dotenv = require("dotenv"); +dotenv.config(); +// require("dotenv").config(); console.log(process.env.PGUSER); const app = express(); +// const number = 8080 const port = process.env.PORT; +const port1= 8080 app.use(cors()); app.use(express.json()); @@ -25,6 +30,6 @@ app.use("/api/articles", create_delete_articleRoutes);//the create_delete will h app.use("/api/courses", courseRoutes); app.use('/api/users', userRoutes); +app.use("/api/images", imagesRoutes); // images routes - -app.listen(port, () => console.log(`app listening on port ${port}`)); +app.listen(port1, () => console.log(`app listening on port ${port1}`)); \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json index 10864c4..0263623 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -16,6 +16,7 @@ "express": "^4.18.2", "firebase-admin": "^12.0.0", "jsonwebtoken": "^9.0.2", + "modules": "^0.4.0", "oauth": "^0.10.0", "pg": "^8.11.3" } @@ -1893,6 +1894,14 @@ "node": ">=10" } }, + "node_modules/modules": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/modules/-/modules-0.4.0.tgz", + "integrity": "sha512-LX4JgwPHJr1FurPDKp1BlGgMXqZXtxO1O8ABGmj2g15CbLGlInTHcA9flqw6uN6oYKE2T0ngWdiHvcX97mdBsw==", + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -4281,6 +4290,11 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" }, + "modules": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/modules/-/modules-0.4.0.tgz", + "integrity": "sha512-LX4JgwPHJr1FurPDKp1BlGgMXqZXtxO1O8ABGmj2g15CbLGlInTHcA9flqw6uN6oYKE2T0ngWdiHvcX97mdBsw==" + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", diff --git a/server/package.json b/server/package.json index 10d1d7c..ef7459a 100644 --- a/server/package.json +++ b/server/package.json @@ -16,6 +16,7 @@ "express": "^4.18.2", "firebase-admin": "^12.0.0", "jsonwebtoken": "^9.0.2", + "modules": "^0.4.0", "oauth": "^0.10.0", "pg": "^8.11.3" } diff --git a/server/src/articles/controller.js b/server/src/articles/controller.js index a0aa9d9..4ee9bf3 100644 --- a/server/src/articles/controller.js +++ b/server/src/articles/controller.js @@ -26,7 +26,6 @@ const addArticles = (req, res) => { const { title,headers,author_id } = req.body; let headers_json = JSON.stringify(headers); - pool.query(queries.addArticles, [title, author_id,headers_json], (error, results) => { if (error) { console.error(error); diff --git a/server/src/articles/routes.js b/server/src/articles/routes.js index 1a0d284..41a1fab 100644 --- a/server/src/articles/routes.js +++ b/server/src/articles/routes.js @@ -10,4 +10,7 @@ const router = Router(); router.get("/", async (req, res) => controller.getArticles(req, res)); router.get("/:id", (req, res) => controller.getArticlesById(req, res)); +router.post("/", (req, res) => controller.addArticles(req, res)); +router.delete('/:id', authorizeArticle, controller.deleteArticle); + module.exports = router; diff --git a/server/src/images/config.js b/server/src/images/config.js new file mode 100644 index 0000000..a5b0fb0 --- /dev/null +++ b/server/src/images/config.js @@ -0,0 +1,13 @@ +const cloudinary = require('cloudinary').v2; +// export const cloudinary1 = cloudinary +cloudinary.config({ + cloud_name: 'dfqmccik2', + api_key: '274176942952433', + api_secret: 'xTN2VnnA_ePTXg9eYlaFu-SNQtw' +}); + +module.exports = { + cloudinary1:cloudinary +} + + diff --git a/server/src/images/controller.js b/server/src/images/controller.js new file mode 100644 index 0000000..9d13cd1 --- /dev/null +++ b/server/src/images/controller.js @@ -0,0 +1,103 @@ +const { createSearchParams } = require("react-router-dom") +const { cloudinary1 } = require("./config"); +const { upload } = require("./multer"); + +const getImages = async (req, res) => { + try { + const images = await cloudinary1.api.resources(); + res.json(images); + console.log(images); + } + catch (err) { + console.error(err); + res.status(500).message(err.message); + } +} + +const getImagesById = async (req, res) => { + const {id} = req.params; + try { + const image = await cloudinary1.api.resource(id); + res.json(image); + } + catch (err) { + console.log(err); + res.status(404).message(err.message); + } + +} + +const postImage = async (req, res) => { + try { + // Use multer's single() middleware to handle single file upload with field name 'image' + upload.single('image')(req, res, async (err) => { + if (err) { + console.error(err); + return res.status(400).json({ + success: false, + message: 'Error uploading image' + }); + } + + // If multer successfully parsed the file, you can proceed with Cloudinary upload + try { + const result = await cloudinary1.uploader.upload(req.file.path); + res.status(200).json({ + success: true, + message: 'Image uploaded successfully', + data: result // Optionally, you can send uploaded image data back to the client + }); + } catch (uploadError) { + console.error(uploadError); + res.status(500).json({ + success: false, + message: 'Error uploading image to Cloudinary' + }); + } + }); + } catch (multerError) { + console.error(multerError); + res.status(500).json({ + success: false, + message: 'Error processing file upload' + }); + } +}; +const deleteImageById = async (req, res) => { + const {id} = req.params; + try { + const image = await cloudinary1.api.resource(id); + if (!image) { + res.status(404).json({ + message: 'Image not found' + }) + } + else { + await cloudinary1.api.delete_resources(id) + .then(resp => { + console.log(resp); + res.status(200).json({ + message: "the image was successfully deleted", + }); + }) + .catch(err => { + console.log(err); + res.status(500).json({ message: "there has been an error deleting the image", }) + + }); + } + } + catch (err) { + console.error(err); + res.status(500).json({ message: err.message }); + + } +} + + +module.exports = { + getImages, + getImagesById, + postImage, + deleteImageById +} \ No newline at end of file diff --git a/server/src/images/multer.js b/server/src/images/multer.js new file mode 100644 index 0000000..2544c91 --- /dev/null +++ b/server/src/images/multer.js @@ -0,0 +1,12 @@ +const multer = require ("multer") +// multer middleware to handle files +const storage = multer.diskStorage({ + filename: function(req,file,cb){ + cb(null, file.originalname) + } +}); +// export const upload = multer({storage: storage}); + +module.exports = { + upload:multer({storage: storage}) +} \ No newline at end of file diff --git a/server/src/images/routes.js b/server/src/images/routes.js new file mode 100644 index 0000000..2dc4160 --- /dev/null +++ b/server/src/images/routes.js @@ -0,0 +1,11 @@ +const {Router} = require("express") +const controller = require("./controller"); +const upload = require("multer") +const router = new Router(); + +router.get("/" , controller.getImages); +router.get("/:id", controller.getImagesById); +router.post("/" , controller.postImage); +router.delete("/:id", controller.deleteImageById); + +module.exports = router; \ No newline at end of file From 6e7d1b8ef9f65bb1e477b491aefee08f29bd42e8 Mon Sep 17 00:00:00 2001 From: Darren52BT Date: Mon, 10 Jun 2024 16:26:11 -0700 Subject: [PATCH 02/28] created basic hooks for bookmarks --- frontend/src/hooks/useCreateBookmark.jsx | 42 ++++++++++++++++++++++++ frontend/src/hooks/useDeleteBookmark.jsx | 39 ++++++++++++++++++++++ frontend/src/hooks/useGetBookmarks.jsx | 39 ++++++++++++++++++++++ frontend/src/pages/HomePage/Home.jsx | 6 ++-- 4 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 frontend/src/hooks/useCreateBookmark.jsx create mode 100644 frontend/src/hooks/useDeleteBookmark.jsx create mode 100644 frontend/src/hooks/useGetBookmarks.jsx diff --git a/frontend/src/hooks/useCreateBookmark.jsx b/frontend/src/hooks/useCreateBookmark.jsx new file mode 100644 index 0000000..023567a --- /dev/null +++ b/frontend/src/hooks/useCreateBookmark.jsx @@ -0,0 +1,42 @@ +import { useState } from "react"; +import { useAuthContext } from "./useAuthContext"; + +export default function useDeleteBookmark() { + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(null); + const { user } = useAuthContext(); + + const createBookmark = async (article_id) => { + if (!user) { + setError("You need to be logged in"); + return; + } + + setIsLoading(true); + setError(null); + + let response = await fetch( + `http://localhost:3002/api/users/bookmarks/${article_id}`, + { + method: "POST", + headers: { + Authorization: `Bearer ${user.token}`, + }, + body: JSON.stringify({ + articleID: article_id, + }), + } + ); + + if (!response.ok) { + setIsLoading(false); + setError(json.error); + } + + if (response.ok) { + setIsLoading(false); + } + }; + + return { createBookmark, isLoading, error }; +} diff --git a/frontend/src/hooks/useDeleteBookmark.jsx b/frontend/src/hooks/useDeleteBookmark.jsx new file mode 100644 index 0000000..235362b --- /dev/null +++ b/frontend/src/hooks/useDeleteBookmark.jsx @@ -0,0 +1,39 @@ +import { useState } from "react"; +import { useAuthContext } from "./useAuthContext"; + +export default function useDeleteBookmark() { + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(null); + const { user } = useAuthContext(); + + const deleteBookmark = async (article_id) => { + if (!user) { + setError("You need to be logged in"); + return; + } + + setIsLoading(true); + setError(null); + + let response = await fetch( + `http://localhost:3002/api/users/bookmarks/${article_id}`, + { + method: "DELETE", + headers: { + Authorization: `Bearer ${user.token}`, + }, + } + ); + + if (!response.ok) { + setIsLoading(false); + setError(json.error); + } + + if (response.ok) { + setIsLoading(false); + } + }; + + return { deleteBookmark, isLoading, error }; +} diff --git a/frontend/src/hooks/useGetBookmarks.jsx b/frontend/src/hooks/useGetBookmarks.jsx new file mode 100644 index 0000000..f898f33 --- /dev/null +++ b/frontend/src/hooks/useGetBookmarks.jsx @@ -0,0 +1,39 @@ +import { useState } from "react"; +import { useAuthContext } from "./useAuthContext"; + +export default function useGetBookmarks() { + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(null); + const { user } = useAuthContext(); + + const getBookmarks = async () => { + if (!user) { + setError("You need to be logged in"); + return; + } + + setIsLoading(true); + setError(null); + const bookmarks = null; + + let response = await fetch("http://localhost:3002/api/users/bookmarks", { + headers: { + Authorization: `Bearer ${user.token}`, + }, + }); + + bookmarks = response.json(); + + if (!response.ok) { + setIsLoading(false); + setError(json.error); + } + if (response.ok) { + setIsLoading(false); + } + + return bookmarks; + }; + + return { getBookmarks, isLoading, error }; +} diff --git a/frontend/src/pages/HomePage/Home.jsx b/frontend/src/pages/HomePage/Home.jsx index 358d6da..ee2842d 100644 --- a/frontend/src/pages/HomePage/Home.jsx +++ b/frontend/src/pages/HomePage/Home.jsx @@ -14,7 +14,6 @@ import devin_ai from "../../assets/Article_Images/devin_ai.png"; import ai_in_business from "../../assets/Article_Images/ai_in_business.png"; import quantum from "../../assets/Article_Images/quantum.png"; import ai_brain from "../../assets/ml_brain.jpg"; - export default function Home() { // demo feature article const feature_article_example = { @@ -74,8 +73,11 @@ export default function Home() { }, ]; + + // useEffect to fetch the main feature article from the database (added later) - useEffect(() => {}, []); + useEffect(() => { + }, []); return ( <> From eb17014167c613fafd1d18e2d39a420da817b6a6 Mon Sep 17 00:00:00 2001 From: davidgit3000 Date: Thu, 13 Jun 2024 11:36:14 -0700 Subject: [PATCH 03/28] added a hook called useBookmark to keep track of bookmark status and toggling functionality --- frontend/src/App.jsx | 2 + frontend/src/Components/Article/Article.jsx | 87 +++++++++++++------- frontend/src/Components/Article/Article.scss | 14 ++++ frontend/src/hooks/useBookmark.jsx | 50 +++++++++++ 4 files changed, 124 insertions(+), 29 deletions(-) create mode 100644 frontend/src/hooks/useBookmark.jsx diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 8943852..d3850bc 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -12,6 +12,7 @@ import ProfileEdit from "./Components/Settings/Profile/ProfileEdit.jsx"; import SavedArticles from "./Components/Settings/SavedArticles/SavedArticles.jsx"; import CustomizationsEdit from "./Components/Settings/Customizations/CustomizationsEdit.jsx"; import SignWelcome from "./pages/SignPages/SignWelcome.jsx"; +import { Toaster } from "react-hot-toast"; function App() { return ( @@ -42,6 +43,7 @@ function App() { /> + ); } diff --git a/frontend/src/Components/Article/Article.jsx b/frontend/src/Components/Article/Article.jsx index f0d5ff2..ab0ac08 100644 --- a/frontend/src/Components/Article/Article.jsx +++ b/frontend/src/Components/Article/Article.jsx @@ -7,6 +7,10 @@ import { Container, Row, Col, Stack } from "react-bootstrap"; import "./ArticleComponents.scss"; import "./Article.scss"; import { useEffect, useState } from "react"; +import useBookmark from "../../hooks/useBookmark.jsx"; +import { AiOutlineLoading3Quarters } from "react-icons/ai"; +import { toast } from "react-hot-toast"; + //dummy data for table of contents const tableOfContents = [ "Introduction to Machine Learning", @@ -74,11 +78,14 @@ const relatedTopicsList = [ //this component accepts an article object and displays the corresponding article export default function Article({ article }) { + const { isLoading, error, updateBookmark } = useBookmark(); + // State to track when the bookmark action is completed + const [bookmarkActionCompleted, setBookmarkActionCompleted] = useState(false); + //extracts article data pieces from provided article //these properties that i'm defining aside from title don't exist in the database yet, //mostly made up so feel free to change later on - //will display default data from figma for now const [articleData, setArticleData] = useState({ @@ -121,39 +128,61 @@ export default function Article({ article }) { //handler for toggling bookmark const toggleBookmark = () => setArticleData({ ...articleData, isBookmarked: !articleData.isBookmarked }); + + // toggleBookmark() function will be called only if there is a successful response from the server + const handleToggleBookmark = () => { + updateBookmark(article.id, article.isBookmarked, toggleBookmark); + }; + + useEffect(() => { + if (error) { + toast.error(error); + } + + if (!isLoading) { + toast.success("Bookmark status updated"); + } + }, [error, isLoading]); + return ( <> - - - - - - + {isLoading ? ( +
+ +
+ ) : ( + + + + + + - - - - - + + + + + - {contentHeaderSequence.map(({ id, heading, body }, index) => { - return ; - })} - - + {contentHeaderSequence.map(({ id, heading, body }, index) => { + return ; + })} + + - - - - - + + + + +
+ )} ); } diff --git a/frontend/src/Components/Article/Article.scss b/frontend/src/Components/Article/Article.scss index a2377e0..69801f6 100644 --- a/frontend/src/Components/Article/Article.scss +++ b/frontend/src/Components/Article/Article.scss @@ -16,6 +16,20 @@ } } +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +.loading { + font-size: 4em; + animation: spin 500ms infinite; +} + @include color-mode(light) { .rel-topics-container { border-color: $divider-light; diff --git a/frontend/src/hooks/useBookmark.jsx b/frontend/src/hooks/useBookmark.jsx new file mode 100644 index 0000000..cfa8003 --- /dev/null +++ b/frontend/src/hooks/useBookmark.jsx @@ -0,0 +1,50 @@ +import { useState } from "react"; +import { useAuthContext } from "./useAuthContext"; + + +export default function useBookmark() { + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const { user } = useAuthContext(); + + const updateBookmark = async (article_id, isBookmarked, toggleBookmark) => { + if (!user) { + setError("You need to be logged in"); + return; + } + + setIsLoading(true); + setError(null); + + try { + const response = await fetch( + `http://localhost:3002/api/bookmarks/${article_id}`, + { + method: isBookmarked ? "DELETE" : "POST", + headers: { + Authorization: `Bearer ${user.token}`, + "Content-Type": isBookmarked ? undefined : "application/json", + }, + body: isBookmarked + ? undefined + : JSON.stringify({ articleID: article_id }), + } + ); + + const json = await response.json(); + + if (json.error) { + throw new Error(json.error || "Failed to toggle bookmark"); + } + + // Call the bookmark toggle function if the request is successful + toggleBookmark(); + } catch (error) { + setError(error.message); + } finally { + setIsLoading(false); + } + }; + + return { isLoading, error, updateBookmark }; +} From 5acab0f7923c2d7bba5cd073ffc2464d65eae802 Mon Sep 17 00:00:00 2001 From: plexin123 <67119125+plexin123@users.noreply.github.com> Date: Tue, 18 Jun 2024 16:59:38 -0700 Subject: [PATCH 04/28] Make some changes about .env and connection refused Make some changes about .env and connection refused --- .../Signin_Signup/Signin/SigninCard.jsx | 124 ++---- .../Signin_Signup/Signup/SignupCard.jsx | 39 +- frontend/src/hooks/useLogin.jsx | 16 +- frontend/src/hooks/useSignup.jsx | 79 ++-- frontend/vite.config.js | 21 +- package-lock.json | 398 +++++++++++++++++- package.json | 3 +- server/db.js | 2 +- server/index.js | 6 +- server/src/users/controller.js | 10 +- 10 files changed, 532 insertions(+), 166 deletions(-) diff --git a/frontend/src/Components/Signin_Signup/Signin/SigninCard.jsx b/frontend/src/Components/Signin_Signup/Signin/SigninCard.jsx index 5bdbd87..fd068f4 100644 --- a/frontend/src/Components/Signin_Signup/Signin/SigninCard.jsx +++ b/frontend/src/Components/Signin_Signup/Signin/SigninCard.jsx @@ -1,10 +1,25 @@ import { useState } from "react"; import { Form, Button, InputGroup, Container, Row, Col } from "react-bootstrap"; import * as auth from "../../auth/auth"; - +import { useLogin } from "../../../hooks/useLogin"; import { EyeFill, EyeSlashFill } from "react-bootstrap-icons"; import "./Signin.scss"; import "../SignForm.scss"; + +const GoogleIcon = () => ( + + {/* SVG content */} + +); + export default function SigninCard() { const [formVal, setFormVal] = useState({ username: "", @@ -12,44 +27,47 @@ export default function SigninCard() { }); const [showPassword, setShowPassword] = useState(false); - //whether form has run through validation yet const [isValidated, setValidated] = useState(false); - - // error messages const [errorMessages, setErrorMessages] = useState({}); + const { login, isLoading, error } = useLogin(); - // handle input entered const handleInput = (e) => { const { name, value } = e.target; - - setFormVal({ - ...formVal, + setFormVal((prevState) => ({ + ...prevState, [name]: value, - }); + })); }; const handlePasswordToggle = () => { setShowPassword(!showPassword); }; - // handle submit - const handleSubmit = (e) => { + const handleSubmit = async (e) => { e.preventDefault(); - const errMessagesList = {}; const checkEmpty = auth.validationFunctions.checkEmpty; - // check empty for now (will add more authentication from backend soon) + // Validation for (const fieldName in formVal) { - let validateResult = checkEmpty(fieldName, formVal[fieldName]); - + const validateResult = checkEmpty(fieldName, formVal[fieldName]); if (typeof validateResult === "string") { errMessagesList[fieldName] = validateResult; } } - setValidated(true); - setErrorMessages(errMessagesList); + if (Object.keys(errMessagesList).length === 0) { + // If no errors, proceed with login + try { + await login(formVal.username, formVal.password); + // Redirect or perform other actions on successful login + } catch (err) { + setErrorMessages({ form: "Invalid credentials" }); + } + } else { + setValidated(true); + setErrorMessages(errMessagesList); + } }; return ( @@ -65,15 +83,13 @@ export default function SigninCard() { name="username" value={formVal.username} placeholder="Username" - isInvalid={errorMessages.hasOwnProperty("username")} + isInvalid={!!errorMessages.username} onChange={handleInput} required className="sign-text-input" /> - {errorMessages.hasOwnProperty("username") - ? errorMessages.username - : ""} + {errorMessages.username || ""} @@ -83,7 +99,7 @@ export default function SigninCard() { value={formVal.password} type={showPassword ? "text" : "password"} placeholder="Password" - isInvalid={errorMessages.hasOwnProperty("password")} + isInvalid={!!errorMessages.password} onChange={handleInput} required className="sign-text-input" @@ -92,21 +108,21 @@ export default function SigninCard() { title={showPassword ? "hide password" : "show password"} className="my-auto bg-white border-white sign-password-visbility-button" onClick={handlePasswordToggle} + type="button" > {showPassword ? : } - {errorMessages.hasOwnProperty("password") - ? errorMessages.password - : ""} + {errorMessages.password || ""} - + {errorMessages.form &&

{errorMessages.form}

} + {error &&

{error}

}
- @@ -144,55 +160,3 @@ export default function SigninCard() { ); } - -const GoogleIcon = () => ( - - - - - - - - - - - -); diff --git a/frontend/src/Components/Signin_Signup/Signup/SignupCard.jsx b/frontend/src/Components/Signin_Signup/Signup/SignupCard.jsx index 95c4037..4a9e4c1 100644 --- a/frontend/src/Components/Signin_Signup/Signup/SignupCard.jsx +++ b/frontend/src/Components/Signin_Signup/Signup/SignupCard.jsx @@ -1,10 +1,10 @@ import { useEffect, useState } from "react"; -import { Card, Form, Button, InputGroup } from "react-bootstrap"; -import { Link, useNavigate } from "react-router-dom"; +import { Form, Button, InputGroup } from "react-bootstrap"; +import { Link } from "react-router-dom"; import * as auth from "../../auth/auth"; import "./Signup.scss"; import "../SignForm.scss"; - +import { useSignup } from "../../../hooks/useSignup"; import { EyeFill, EyeSlashFill } from "react-bootstrap-icons"; export default function SignupCard() { @@ -18,15 +18,17 @@ export default function SignupCard() { }); const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); - const navigate = useNavigate(); - //whether form has run through validation yet + // Whether form has run through validation yet const [isValidated, setValidated] = useState(false); - // error messages + // Error messages const [errorMessages, setErrorMessages] = useState({}); - // handle input entered + // Import the useSignup hook + const { signup, isLoading, error } = useSignup(); + + // Handle input entered const handleInput = (e) => { const { name, value } = e.target; @@ -45,18 +47,11 @@ export default function SignupCard() { }; const isValidationPassed = () => { - return Object.keys(errorMessages).length === 0 ? true : false; + return Object.keys(errorMessages).length === 0; }; - useEffect(() => { - // might add API endpoints to handle backend authentication here - if (isValidated && isValidationPassed()) { - navigate("/signin"); - } - }, [isValidationPassed]); - - // handle submit - const handleSubmit = (e) => { + // Handle submit + const handleSubmit = async (e) => { e.preventDefault(); const newErrMessages = {}; @@ -80,6 +75,11 @@ export default function SignupCard() { setValidated(true); setErrorMessages(newErrMessages); + + // Call the signup function if no validation errors + if (isValidationPassed()) { + await signup(formVal.username, formVal.password); + } }; return ( @@ -204,10 +204,11 @@ export default function SignupCard() { + {error &&
{error}
}
-
diff --git a/frontend/src/hooks/useLogin.jsx b/frontend/src/hooks/useLogin.jsx index b821e2c..89ff9a1 100644 --- a/frontend/src/hooks/useLogin.jsx +++ b/frontend/src/hooks/useLogin.jsx @@ -1,23 +1,29 @@ import { useState } from "react"; import { useAuthContext } from "./useAuthContext"; + export const useLogin = () => { const [error, setError] = useState(null) - const [isLoading, setIsLoading] = useState(null) + const [isLoading, setIsLoading] = useState(false) const { dispatch } = useAuthContext() - - const login = async (username,user_password) => + const apiUrl = import.meta.env.VITE_API_URL; //change to .env + // const api = process.env.REACT_APP_API_URL + + + const login = async (username,user_password) => { setIsLoading(true); setError(null); - - const response = await fetch('http://localhost:3002/api/users/login', + const response = await fetch(`${apiUrl}/api/users/login`, { method : 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ username, user_password }) + }) + console.log(apiUrl) + const json = await response.json(); diff --git a/frontend/src/hooks/useSignup.jsx b/frontend/src/hooks/useSignup.jsx index e9d989c..c04fecc 100644 --- a/frontend/src/hooks/useSignup.jsx +++ b/frontend/src/hooks/useSignup.jsx @@ -1,40 +1,57 @@ import { useState } from "react"; import { useAuthContext } from "./useAuthContext"; +import { useNavigate } from "react-router-dom"; -export const useSignup = () => -{ - const [error, setError] = useState(null) - const [isLoading, setIsLoading] = useState(null) - const { dispatch } = useAuthContext() - const signup = async (username,user_password) => - { - setIsLoading(true); - setError(null); +export const useSignup = () => { + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const { dispatch } = useAuthContext(); + const navigate = useNavigate(); + const apiUrl = import.meta.env.VITE_API_URL; + // const api = process.env.REACT_APP_API_URL + - const response = await fetch('http://localhost:3002/api/users/', - { - method : 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({ username, user_password }) - }) + console.log("API URL from environment:", apiUrl); // Debug log - const json = await response.json(); - - if (!response.ok) { - setIsLoading(false) - setError(json.error) - } - if (response.ok) { - //save user to local storage - localStorage.setItem('user', JSON.stringify(json)) - - //update authcontext - dispatch({type: 'LOGIN', payload: json}) + const signup = async (username, user_password) => { + setIsLoading(true); + setError(null); - setIsLoading(false) + try { + const response = await fetch(`http://localhost:3002/api/users`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username, user_password }) + }); + + let json; + try { + json = await response.json(); + } catch (err) { + setError("Invalid response from server"); + setIsLoading(false); + return; + } + + if (!response.ok) { + setError(json.error); + setIsLoading(false); + return; + } + + // Save user to local storage + localStorage.setItem('user', JSON.stringify(json)); + + // Update auth context + dispatch({ type: 'LOGIN', payload: json }); + + navigate("/signin"); + } catch (err) { + setError("Something went wrong. Please try again."); + setIsLoading(false); } - } + }; - return {signup, isLoading, error} -} \ No newline at end of file + return { signup, isLoading, error }; +}; diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 081c8d9..a93e4a2 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -1,6 +1,17 @@ -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react"; +import { defineConfig, loadEnv } from 'vite'; +import react from '@vitejs/plugin-react'; -export default defineConfig({ - plugins: [react()], -}); +export default ({ mode }) => { + // Load environment variables based on the current mode + const env = loadEnv(mode, process.cwd()); + + return defineConfig({ + plugins: [react()], + define: { + // Make sure to stringify the environment variables + 'process.env': { + VITE_API_URL: JSON.stringify(env.VITE_API_URL), + }, + }, + }); +}; diff --git a/package-lock.json b/package-lock.json index a593783..08b675a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,9 +15,10 @@ "dotenv": "^16.4.5", "express": "^4.18.2", "jsonwebtoken": "^9.0.2", + "man": "^2.0.0", "modules": "^0.4.0", "multer": "^1.4.5-lts.1", - "man": "^2.0.0", + "react-bootstrap-typeahead": "^6.3.2", "vite": "^5.0.12" }, "devDependencies": { @@ -441,7 +442,6 @@ "version": "7.23.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", - "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -883,6 +883,15 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@remix-run/router": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.11.0.tgz", @@ -892,6 +901,17 @@ "node": ">=14.0.0" } }, + "node_modules/@restart/hooks": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", + "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.9.6", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.6.tgz", @@ -1090,6 +1110,25 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" }, + "node_modules/@types/prop-types": { + "version": "15.7.12", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" + }, + "node_modules/@types/react": { + "version": "18.3.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", + "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/warning": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", + "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==" + }, "node_modules/@vitejs/plugin-react": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz", @@ -1393,6 +1432,11 @@ "fsevents": "~2.3.2" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -1437,6 +1481,11 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz", + "integrity": "sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1538,6 +1587,11 @@ "node": ">= 0.10" } }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, "node_modules/date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", @@ -1596,6 +1650,14 @@ "node": ">= 0.8" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -1605,6 +1667,15 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "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/dotenv": { "version": "16.4.5", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", @@ -1768,6 +1839,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -2005,6 +2081,14 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -2141,6 +2225,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -2180,8 +2269,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -2515,6 +2602,16 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -2582,7 +2679,6 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dev": true, "peer": true, "dependencies": { "loose-envify": "^1.1.0" @@ -2591,11 +2687,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-bootstrap-typeahead": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/react-bootstrap-typeahead/-/react-bootstrap-typeahead-6.3.2.tgz", + "integrity": "sha512-N5Mb0WlSSMcD7Z0pcCypILgIuECybev0hl4lsnCa5lbXTnN4QdkuHLGuTLSlXBwm1ZMFpOc2SnsdSRgeFiF+Ow==", + "dependencies": { + "@babel/runtime": "^7.14.6", + "@popperjs/core": "^2.10.2", + "@restart/hooks": "^0.4.0", + "classnames": "^2.2.0", + "fast-deep-equal": "^3.1.1", + "invariant": "^2.2.1", + "lodash.debounce": "^4.0.8", + "prop-types": "^15.5.8", + "react-overlays": "^5.2.0", + "react-popper": "^2.2.5", + "scroll-into-view-if-needed": "^3.1.0", + "warning": "^4.0.1" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "dev": true, "peer": true, "dependencies": { "loose-envify": "^1.1.0", @@ -2605,6 +2726,54 @@ "react": "^18.2.0" } }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + }, + "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/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "node_modules/react-overlays": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-5.2.1.tgz", + "integrity": "sha512-GLLSOLWr21CqtJn8geSwQfoJufdt3mfdsnIiQswouuQ2MMPns+ihZklxvsTDKD3cR2tF8ELbi5xUsvqVhR6WvA==", + "dependencies": { + "@babel/runtime": "^7.13.8", + "@popperjs/core": "^2.11.6", + "@restart/hooks": "^0.4.7", + "@types/warning": "^3.0.0", + "dom-helpers": "^5.2.0", + "prop-types": "^15.7.2", + "uncontrollable": "^7.2.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + } + }, + "node_modules/react-popper": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", + "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", + "dependencies": { + "react-fast-compare": "^3.0.1", + "warning": "^4.0.2" + }, + "peerDependencies": { + "@popperjs/core": "^2.0.0", + "react": "^16.8.0 || ^17 || ^18", + "react-dom": "^16.8.0 || ^17 || ^18" + } + }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -2679,8 +2848,7 @@ "node_modules/regenerator-runtime": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", - "dev": true + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" }, "node_modules/require-directory": { "version": "2.1.1", @@ -2776,12 +2944,19 @@ "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "dev": true, "peer": true, "dependencies": { "loose-envify": "^1.1.0" } }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -3055,6 +3230,20 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, + "node_modules/uncontrollable": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", + "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", + "dependencies": { + "@babel/runtime": "^7.6.3", + "@types/react": ">=16.9.11", + "invariant": "^2.2.4", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": ">=15.0.0" + } + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -3173,6 +3362,14 @@ } } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -3551,7 +3748,6 @@ "version": "7.23.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", - "dev": true, "requires": { "regenerator-runtime": "^0.14.0" } @@ -3765,12 +3961,25 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" + }, "@remix-run/router": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.11.0.tgz", "integrity": "sha512-BHdhcWgeiudl91HvVa2wxqZjSHbheSgIiDvxrF1VjFzBzpTtuDPkOdOi3Iqvc08kXtFkLjhbS+ML9aM8mJS+wQ==", "dev": true }, + "@restart/hooks": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", + "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", + "requires": { + "dequal": "^2.0.3" + } + }, "@rollup/rollup-android-arm-eabi": { "version": "4.9.6", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.6.tgz", @@ -3891,6 +4100,25 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" }, + "@types/prop-types": { + "version": "15.7.12", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" + }, + "@types/react": { + "version": "18.3.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", + "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", + "requires": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "@types/warning": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", + "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==" + }, "@vitejs/plugin-react": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz", @@ -4104,6 +4332,11 @@ "readdirp": "~3.6.0" } }, + "classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, "cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -4139,6 +4372,11 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "compute-scroll-into-view": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz", + "integrity": "sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -4215,6 +4453,11 @@ "vary": "^1" } }, + "csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, "date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", @@ -4254,11 +4497,25 @@ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" }, + "dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" + }, "destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, + "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" + } + }, "dotenv": { "version": "16.4.5", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", @@ -4396,6 +4653,11 @@ } } }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -4568,6 +4830,14 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "^1.0.0" + } + }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -4670,6 +4940,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, "lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -4709,8 +4984,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "peer": true, "requires": { "js-tokens": "^3.0.0 || ^4.0.0" } @@ -4926,6 +5199,16 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -4974,23 +5257,79 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dev": true, "peer": true, "requires": { "loose-envify": "^1.1.0" } }, + "react-bootstrap-typeahead": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/react-bootstrap-typeahead/-/react-bootstrap-typeahead-6.3.2.tgz", + "integrity": "sha512-N5Mb0WlSSMcD7Z0pcCypILgIuECybev0hl4lsnCa5lbXTnN4QdkuHLGuTLSlXBwm1ZMFpOc2SnsdSRgeFiF+Ow==", + "requires": { + "@babel/runtime": "^7.14.6", + "@popperjs/core": "^2.10.2", + "@restart/hooks": "^0.4.0", + "classnames": "^2.2.0", + "fast-deep-equal": "^3.1.1", + "invariant": "^2.2.1", + "lodash.debounce": "^4.0.8", + "prop-types": "^15.5.8", + "react-overlays": "^5.2.0", + "react-popper": "^2.2.5", + "scroll-into-view-if-needed": "^3.1.0", + "warning": "^4.0.1" + } + }, "react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "dev": true, "peer": true, "requires": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" } }, + "react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + }, + "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==" + }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "react-overlays": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-5.2.1.tgz", + "integrity": "sha512-GLLSOLWr21CqtJn8geSwQfoJufdt3mfdsnIiQswouuQ2MMPns+ihZklxvsTDKD3cR2tF8ELbi5xUsvqVhR6WvA==", + "requires": { + "@babel/runtime": "^7.13.8", + "@popperjs/core": "^2.11.6", + "@restart/hooks": "^0.4.7", + "@types/warning": "^3.0.0", + "dom-helpers": "^5.2.0", + "prop-types": "^15.7.2", + "uncontrollable": "^7.2.1", + "warning": "^4.0.3" + } + }, + "react-popper": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", + "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", + "requires": { + "react-fast-compare": "^3.0.1", + "warning": "^4.0.2" + } + }, "react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -5048,8 +5387,7 @@ "regenerator-runtime": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", - "dev": true + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" }, "require-directory": { "version": "2.1.1", @@ -5113,12 +5451,19 @@ "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "dev": true, "peer": true, "requires": { "loose-envify": "^1.1.0" } }, + "scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "requires": { + "compute-scroll-into-view": "^3.0.2" + } + }, "semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -5335,6 +5680,17 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, + "uncontrollable": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", + "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", + "requires": { + "@babel/runtime": "^7.6.3", + "@types/react": ">=16.9.11", + "invariant": "^2.2.4", + "react-lifecycles-compat": "^3.0.4" + } + }, "undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -5381,6 +5737,14 @@ "rollup": "^4.2.0" } }, + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/package.json b/package.json index 025f23e..374bab7 100644 --- a/package.json +++ b/package.json @@ -33,9 +33,10 @@ "dotenv": "^16.4.5", "express": "^4.18.2", "jsonwebtoken": "^9.0.2", + "man": "^2.0.0", "modules": "^0.4.0", "multer": "^1.4.5-lts.1", - "man": "^2.0.0", + "react-bootstrap-typeahead": "^6.3.2", "vite": "^5.0.12" } } diff --git a/server/db.js b/server/db.js index 7541cf7..6f76e93 100644 --- a/server/db.js +++ b/server/db.js @@ -9,7 +9,7 @@ const pool = new Pool( host: process.env.PGHOST, database: process.env.PGDATABASE, password: process.env.PGPASSWORD, - port: process.env.PGPORT, + port: process.env.PGPORT, } ) diff --git a/server/index.js b/server/index.js index 7306422..e824652 100644 --- a/server/index.js +++ b/server/index.js @@ -16,7 +16,7 @@ const app = express(); const port = process.env.PORT; -const port1= 8080 +// const port1= 8080 app.use(cors()); app.use(express.json()); @@ -29,7 +29,7 @@ app.use("/api/articles", articleRoutes);// must go BEFORE create_delete because app.use("/api/articles", create_delete_articleRoutes);//the create_delete will have authorization checked, but the regular articleRoutes will not app.use("/api/courses", courseRoutes); -app.use('/api/users', userRoutes); +app.use("/api/users", userRoutes); app.use("/api/images", imagesRoutes); // images routes -app.listen(port1, () => console.log(`app listening on port ${port1}`)); \ No newline at end of file +app.listen(port, () => console.log(`app listening on port ${port}`)); \ No newline at end of file diff --git a/server/src/users/controller.js b/server/src/users/controller.js index f2a7afd..79f93de 100644 --- a/server/src/users/controller.js +++ b/server/src/users/controller.js @@ -57,10 +57,11 @@ const loginUser = async (req, res) => { if(result.rows.length === 0){ return res.status(400).send("Error finding username") } - const user = result.rows[0]; - console.log("password:", user_password); - console.log("password hased:", user.user_password); - if(await bcrypt.compare(user_password, user.user_password)){ + const user = result.rows[0]; //find the first user + // console.log(user); + console.log("password:", user_password); //body request user_password, how response password + console.log("password hashed:", user.password); + if(await bcrypt.compare(user_password, user.password)){ //res.send("Success"); const token = createToken(username); res.json({username,token}) @@ -70,6 +71,7 @@ const loginUser = async (req, res) => { } } catch (error) { res.status(500).send(); + console.log(error); } } From 02b7ecc27eb5ac87d9b437f69bccc60f9bf3cc78 Mon Sep 17 00:00:00 2001 From: plexin123 <67119125+plexin123@users.noreply.github.com> Date: Wed, 19 Jun 2024 14:29:34 -0700 Subject: [PATCH 05/28] login/signup 90% fix some issues --- frontend/src/hooks/useLogin.jsx | 13 ++++++++++++- frontend/src/hooks/useSignup.jsx | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/frontend/src/hooks/useLogin.jsx b/frontend/src/hooks/useLogin.jsx index 89ff9a1..1bed9b7 100644 --- a/frontend/src/hooks/useLogin.jsx +++ b/frontend/src/hooks/useLogin.jsx @@ -1,5 +1,6 @@ import { useState } from "react"; import { useAuthContext } from "./useAuthContext"; +import { Navigate, useNavigate } from "react-router-dom"; export const useLogin = () => @@ -7,14 +8,16 @@ export const useLogin = () => const [error, setError] = useState(null) const [isLoading, setIsLoading] = useState(false) const { dispatch } = useAuthContext() + const navigate = useNavigate() const apiUrl = import.meta.env.VITE_API_URL; //change to .env // const api = process.env.REACT_APP_API_URL - const login = async (username,user_password) => + const login = async (username,user_password,error) => { setIsLoading(true); setError(null); + try { const response = await fetch(`${apiUrl}/api/users/login`, { method : 'POST', @@ -39,8 +42,16 @@ export const useLogin = () => dispatch({type: 'LOGIN', payload: json}) setIsLoading(false) + console.log(localStorage) + navigate("/") } + + } + catch (err) { + setError("Something went wrong. Please try again."); + setIsLoading(false); } +} return {login, isLoading, error} } \ No newline at end of file diff --git a/frontend/src/hooks/useSignup.jsx b/frontend/src/hooks/useSignup.jsx index c04fecc..e8466e6 100644 --- a/frontend/src/hooks/useSignup.jsx +++ b/frontend/src/hooks/useSignup.jsx @@ -19,7 +19,7 @@ export const useSignup = () => { setError(null); try { - const response = await fetch(`http://localhost:3002/api/users`, { + const response = await fetch(`${apiUrl}/api/users`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, user_password }) From 4fbea6977fb1b71b86ade4dd34bd316701c86b4b Mon Sep 17 00:00:00 2001 From: davidgit3000 Date: Wed, 19 Jun 2024 17:31:30 -0700 Subject: [PATCH 06/28] fixed the fetch link between DELETE and POST methods in useBookmark hook --- frontend/src/Components/Article/Article.jsx | 7 +++---- frontend/src/hooks/useBookmark.jsx | 5 +++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/Components/Article/Article.jsx b/frontend/src/Components/Article/Article.jsx index ab0ac08..3e0f4ba 100644 --- a/frontend/src/Components/Article/Article.jsx +++ b/frontend/src/Components/Article/Article.jsx @@ -79,8 +79,6 @@ const relatedTopicsList = [ //this component accepts an article object and displays the corresponding article export default function Article({ article }) { const { isLoading, error, updateBookmark } = useBookmark(); - // State to track when the bookmark action is completed - const [bookmarkActionCompleted, setBookmarkActionCompleted] = useState(false); //extracts article data pieces from provided article @@ -125,6 +123,7 @@ export default function Article({ article }) { }; } ); + //handler for toggling bookmark const toggleBookmark = () => setArticleData({ ...articleData, isBookmarked: !articleData.isBookmarked }); @@ -138,8 +137,8 @@ export default function Article({ article }) { if (error) { toast.error(error); } - - if (!isLoading) { + + if (!isLoading && articleData.isBookmarked) { toast.success("Bookmark status updated"); } }, [error, isLoading]); diff --git a/frontend/src/hooks/useBookmark.jsx b/frontend/src/hooks/useBookmark.jsx index cfa8003..9981e73 100644 --- a/frontend/src/hooks/useBookmark.jsx +++ b/frontend/src/hooks/useBookmark.jsx @@ -1,7 +1,6 @@ import { useState } from "react"; import { useAuthContext } from "./useAuthContext"; - export default function useBookmark() { const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(false); @@ -18,7 +17,9 @@ export default function useBookmark() { try { const response = await fetch( - `http://localhost:3002/api/bookmarks/${article_id}`, + isBookmarked + ? `http://localhost:3002/api/bookmarks/${article_id}` + : "http://localhost:3002/api/bookmarks", { method: isBookmarked ? "DELETE" : "POST", headers: { From 4bcedba0143a2e663066d5651e7c0d0a0467b4dc Mon Sep 17 00:00:00 2001 From: davidgit3000 Date: Wed, 19 Jun 2024 21:04:49 -0700 Subject: [PATCH 07/28] added useBookmark hook to handle bookmark toggling logic --- .../Article/ArticleHeader/ArticleHeader.jsx | 7 +- .../RecentArticleItem/RecentArticleItem.jsx | 8 +- .../Home/RecentArticles/RecentArticles.jsx | 75 +++++++--- .../Settings/SavedArticles/SavedArticles.jsx | 55 ++++++-- frontend/src/hooks/useBookmark.jsx | 16 +-- .../ArticleResultsPage/ArticleResultsPage.jsx | 130 ++++++++++++------ 6 files changed, 196 insertions(+), 95 deletions(-) diff --git a/frontend/src/Components/Article/ArticleHeader/ArticleHeader.jsx b/frontend/src/Components/Article/ArticleHeader/ArticleHeader.jsx index 5935b14..8ffdebe 100644 --- a/frontend/src/Components/Article/ArticleHeader/ArticleHeader.jsx +++ b/frontend/src/Components/Article/ArticleHeader/ArticleHeader.jsx @@ -33,12 +33,7 @@ export default function ArticleHeader({ bookmarkToggler={bookmarkToggler} /> - + ); } diff --git a/frontend/src/Components/Home/RecentArticles/RecentArticleItem/RecentArticleItem.jsx b/frontend/src/Components/Home/RecentArticles/RecentArticleItem/RecentArticleItem.jsx index dd0fc1b..c0dc80b 100644 --- a/frontend/src/Components/Home/RecentArticles/RecentArticleItem/RecentArticleItem.jsx +++ b/frontend/src/Components/Home/RecentArticles/RecentArticleItem/RecentArticleItem.jsx @@ -26,12 +26,12 @@ export default function RecentArticleItem({
{toBeBookmarked ? ( - ) : ( - @@ -44,7 +44,9 @@ export default function RecentArticleItem({ {/*

{articleDesc}

*/}

By {articleAuthor}

-

Published {articleDatePublished}

+

+ Published {articleDatePublished} +

diff --git a/frontend/src/Components/Home/RecentArticles/RecentArticles.jsx b/frontend/src/Components/Home/RecentArticles/RecentArticles.jsx index 0bba7ac..e740dc4 100644 --- a/frontend/src/Components/Home/RecentArticles/RecentArticles.jsx +++ b/frontend/src/Components/Home/RecentArticles/RecentArticles.jsx @@ -3,8 +3,13 @@ import "./RecentArticles.scss"; import { useEffect, useState } from "react"; import RecentArticleItem from "./RecentArticleItem/RecentArticleItem"; +import useBookmark from "../../../hooks/useBookmark"; +import toast from "react-hot-toast"; +import { AiOutlineLoading3Quarters } from "react-icons/ai"; export default function RecentArticles({ recent_articles }) { + const { isLoading, error, updateBookmark } = useBookmark(); + //array all the articles currently not deleted const [articles, setArticles] = useState([]); @@ -18,22 +23,42 @@ export default function RecentArticles({ recent_articles }) { return { ...prevArticles, [id]: !prevArticles[id] }; }); + const handleToggleBookmark = (article_id) => { + const toggleBookmark = () => articleToggleHandler(article_id); + updateBookmark( + article_id, + isBookmarkedArticles[article_id], + toggleBookmark + ); + }; + //use effect to get articles upon page load once, also init selected state of every article as false //just simulating retrieving articles useEffect(() => { - let initArticles = async () => { - let retrieved_articles = await recent_articles; - setArticles(retrieved_articles); + const initArticles = async () => { + try { + const retrieved_articles = await recent_articles; + setArticles(retrieved_articles); - let initIsBookmarkedArticles = {}; - retrieved_articles.forEach(({ id }) => { - initIsBookmarkedArticles[id] = false; - }); - setIsBookmarkedArticles(initIsBookmarkedArticles); + const initIsBookmarkedArticles = {}; + retrieved_articles.forEach(({ id }) => { + initIsBookmarkedArticles[id] = false; + }); + setIsBookmarkedArticles(initIsBookmarkedArticles); + } catch (error) { + toast.error(error.message); + } }; initArticles(); - }, []); + }, [recent_articles]); + + // Handle errors from the bookmark hook + useEffect(() => { + if (error) { + toast.error(error); + } + }, [error]); return ( <> @@ -41,19 +66,25 @@ export default function RecentArticles({ recent_articles }) {

Recent Articles

- {recent_articles.map((article) => ( - - ))} + {isLoading ? ( +
+ +
+ ) : ( + articles.map((article) => ( + handleToggleBookmark(article.id)} + /> + )) + )} {/** May add onClick function to fetch more articles */}
diff --git a/frontend/src/Components/Settings/SavedArticles/SavedArticles.jsx b/frontend/src/Components/Settings/SavedArticles/SavedArticles.jsx index 0d8788c..9d46548 100644 --- a/frontend/src/Components/Settings/SavedArticles/SavedArticles.jsx +++ b/frontend/src/Components/Settings/SavedArticles/SavedArticles.jsx @@ -5,6 +5,9 @@ import ConfirmDeleteModal from "./ConfirmDeleteModal"; import ArrowMarker from "../../ArrowMarker/ArrowMarker"; import "../Settings.scss"; import SavedArticlesSearchBar from "./SearchBar/SavedArticlesSearchBar"; +import useBookmark from "../../../hooks/useBookmark"; +import { AiOutlineLoading3Quarters } from "react-icons/ai"; +import toast from "react-hot-toast"; const test_articles = [ { id: 0, @@ -30,6 +33,8 @@ const test_articles = [ ]; export default function SavedArticles() { + const { isLoading, error, updateBookmark } = useBookmark(); + //array all the articles currently not deleted const [articles, setArticles] = useState([]); @@ -54,7 +59,12 @@ export default function SavedArticles() { //use effect to get articles upon page load once, also init selected state of every article as false //just simulating retrieving articles + // Show any error from the server if possible useEffect(() => { + if (error) { + toast.error(error); + } + let initArticles = async () => { let retrieved_articles = await test_articles; setArticles(retrieved_articles); @@ -67,12 +77,11 @@ export default function SavedArticles() { }; initArticles(); - }, []); + }, [error]); - //submit handler (the yes button in modal does not trigger submit event) //simply removed the selected articles from the displayed articles state //insert backend actions here - const submitHandler = () => { + const updateSavedArticles = () => { //filter out kept articles, replace articles state with them let keptArticles = articles.filter( (article) => !isDeletedArticles[article.id] @@ -87,6 +96,18 @@ export default function SavedArticles() { setIsDeletedArticles(initIsDeletedArticles); }; + // Submit handler (the yes button in modal does not trigger submit event) + // The function updateSavedArticles will be only executed if the server returns a response successfully + const submitHandler = () => { + articles.forEach((article, article_id) => + updateBookmark( + article_id, + isDeletedArticles[article_id], + updateSavedArticles + ) + ); + }; + //state for whether delete confirmation modal is displayed or now const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); @@ -118,7 +139,9 @@ export default function SavedArticles() {
-

My Bookmarks

+

+ My Bookmarks +

- {articles.map((article) => ( - - ))} + {isLoading ? ( +
+ +
+ ) : ( + articles.map((article) => ( + + )) + )}
{/*Remove/Cancel will only show if there are any articles selected to be deleted*/} {Object.values(isDeletedArticles).some((isDeleted) => isDeleted) && ( diff --git a/frontend/src/hooks/useBookmark.jsx b/frontend/src/hooks/useBookmark.jsx index 9981e73..3e2304d 100644 --- a/frontend/src/hooks/useBookmark.jsx +++ b/frontend/src/hooks/useBookmark.jsx @@ -7,15 +7,15 @@ export default function useBookmark() { const { user } = useAuthContext(); const updateBookmark = async (article_id, isBookmarked, toggleBookmark) => { - if (!user) { - setError("You need to be logged in"); - return; - } - - setIsLoading(true); - setError(null); - try { + if (!user) { + setError("You need to be logged in"); + return; + } + + setIsLoading(true); + setError(null); + const response = await fetch( isBookmarked ? `http://localhost:3002/api/bookmarks/${article_id}` diff --git a/frontend/src/pages/ArticleResultsPage/ArticleResultsPage.jsx b/frontend/src/pages/ArticleResultsPage/ArticleResultsPage.jsx index 2ee102a..35379fe 100644 --- a/frontend/src/pages/ArticleResultsPage/ArticleResultsPage.jsx +++ b/frontend/src/pages/ArticleResultsPage/ArticleResultsPage.jsx @@ -1,9 +1,13 @@ import { useEffect, useState } from "react"; import { useParams, useSearchParams } from "react-router-dom"; import RelatedTags from "../../Components/ArticleResults/SideSections/RelatedTopicTags/RelatedTopicTags.jsx"; -import ArticleResultsList from "../../Components/ArticleResults/ArticleResultsList.jsx"; import { Col, Container, Row } from "react-bootstrap"; import "./ArticleResultsPage.scss"; +import { AiOutlineLoading3Quarters } from "react-icons/ai"; +import toast from "react-hot-toast"; +import ArticleResult from "../../Components/ArticleResults/ArticleResult/ArticleResult.jsx"; +import useBookmark from "../../hooks/useBookmark.jsx"; + const dummy_topic_tags = [ { label: "Deep Learning" }, { label: "Artifical Intelligence" }, @@ -23,7 +27,9 @@ const dummmy_articles = [ ]; export default function ArticleResultsPage({}) { - const [articles, setArticles] = useState(); + const { isLoading, error, updateBookmark } = useBookmark(); + + const [articles, setArticles] = useState([]); const [searchParams, setSearchParams] = useSearchParams(); const [specificArticle, setSpecificArticle] = useState(); const titleQuery = searchParams.get("title"); @@ -39,51 +45,89 @@ export default function ArticleResultsPage({}) { } }; + const handleToggleBookmark = (article_id) => { + const article = articles.find((a) => a.id === article_id); + if (!article) return; + + const toggleBookmark = bookmarkTogglerCreator(article_id); + updateBookmark(article_id, article.isBookmarked, toggleBookmark); + }; + useEffect(() => { - fetch(`http://localhost:3002/api/articles/?title=${titleQuery}`) - .then((res) => - res.json().then((data) => { - let dataCopy = [...data]; + const fetchArticles = async () => { + try { + const response = await fetch( + `http://localhost:3002/api/articles/?title=${titleQuery}` + ); + const data = await response.json(); - //we dont have author names, date, bookmarked, or image, so just inserting default in for now - dataCopy.forEach((articleObject) => { - articleObject.image = - "https://emeritus.org/in/wp-content/uploads/sites/3/2023/03/types-of-machine-learning.jpg.optimal.jpg"; - articleObject.author = "jeff"; - articleObject.date = "October 24, 2023"; - articleObject.isBookmarked = false; - }); - setArticles(dataCopy); - }) - ) - .catch((error) => { - console.error("error fetching data"); - }); + const enrichedData = data.map((articleObject) => ({ + ...articleObject, + image: + "https://emeritus.org/in/wp-content/uploads/sites/3/2023/03/types-of-machine-learning.jpg.optimal.jpg", + author: "jeff", + date: "October 24, 2023", + isBookmarked: false, + })); + + setArticles(enrichedData); + } catch (error) { + toast.error(error.message); + } + }; + + fetchArticles(); }, [titleQuery, setSearchParams]); + // Handle errors from the bookmark hook + useEffect(() => { + if (error) { + toast.error(error); + } + }, [error]); + return ( - - - -

- Displaying results for{" "} - "{titleQuery}" -

- - - - - -
-
+ <> + {isLoading ? ( +
+ +
+ ) : ( + + + +

+ Displaying results for{" "} + + "{titleQuery}" + +

+
+ {articles && articles.length > 0 ? ( + articles.map((article) => ( + handleToggleBookmark(article.id)} + /> + )) + ) : ( +

Your query did not match any results

+ )} +
+ + + + +
+
+ )} + ); } From d8c67be6dad8d967a3638d81e81f06c35b6e12db Mon Sep 17 00:00:00 2001 From: davidgit3000 Date: Wed, 19 Jun 2024 21:30:21 -0700 Subject: [PATCH 08/28] added signup logic using useSignup hook --- .../Signin_Signup/Signup/SignupCard.jsx | 44 +++++++++++++------ 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/frontend/src/Components/Signin_Signup/Signup/SignupCard.jsx b/frontend/src/Components/Signin_Signup/Signup/SignupCard.jsx index 95c4037..2ce38d6 100644 --- a/frontend/src/Components/Signin_Signup/Signup/SignupCard.jsx +++ b/frontend/src/Components/Signin_Signup/Signup/SignupCard.jsx @@ -6,8 +6,12 @@ import "./Signup.scss"; import "../SignForm.scss"; import { EyeFill, EyeSlashFill } from "react-bootstrap-icons"; +import { useSignup } from "../../../hooks/useSignup"; +import toast from "react-hot-toast"; export default function SignupCard() { + const { signup, isLoading, error } = useSignup(); + const [formVal, setFormVal] = useState({ username: "", fname: "", @@ -48,17 +52,13 @@ export default function SignupCard() { return Object.keys(errorMessages).length === 0 ? true : false; }; - useEffect(() => { - // might add API endpoints to handle backend authentication here - if (isValidated && isValidationPassed()) { - navigate("/signin"); - } - }, [isValidationPassed]); - - // handle submit - const handleSubmit = (e) => { - e.preventDefault(); - + // useEffect(() => { + // // might add API endpoints to handle backend authentication here + // if (isValidated && isValidationPassed()) { + // navigate("/"); + // } + // }, [isValidationPassed]); + const validateInputField = () => { const newErrMessages = {}; const formValidation = auth.formValidation; @@ -82,6 +82,22 @@ export default function SignupCard() { setErrorMessages(newErrMessages); }; + // handle submit + const handleSubmit = (e) => { + e.preventDefault(); + + try { + validateInputField(); + + if (isValidated && isValidationPassed) { + signup(formVal.username, formVal.password); + navigate("/"); + } + } catch (error) { + toast.error(error.message); + } + }; + return (
-

Sign Up

+

+ Sign Up +

-