From 0046d516e4b629a636fa319a76c743c7e982df51 Mon Sep 17 00:00:00 2001 From: Grayson Marie Smith Date: Sun, 27 Oct 2024 13:59:40 -0500 Subject: [PATCH 01/27] initial commits --- app/src/components/Navbar.tsx | 8 +++++ app/src/pages/CreateMeeting.tsx | 60 +++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 app/src/pages/CreateMeeting.tsx diff --git a/app/src/components/Navbar.tsx b/app/src/components/Navbar.tsx index 04c64f4..cc91215 100644 --- a/app/src/components/Navbar.tsx +++ b/app/src/components/Navbar.tsx @@ -38,6 +38,14 @@ const Navbar = (): ReactElement => { > Create Workshop +
{ + navigate("/create-meeting") + }} + > + Create Meeting +
diff --git a/app/src/pages/CreateMeeting.tsx b/app/src/pages/CreateMeeting.tsx new file mode 100644 index 0000000..10f3ff3 --- /dev/null +++ b/app/src/pages/CreateMeeting.tsx @@ -0,0 +1,60 @@ +import React, { useState } from "react" +import { Formik, Form, Field, FieldArray } from "formik" +import Navbar from "../components/Navbar" +import * as Yup from "yup" + +const CreateMeeting = () => { + const initialValues = { + name: "", + description: "", + } + const validationSchema = Yup.object().shape({ + name: Yup.string().required("Name is required"), + description: Yup.string().required("Description is required"), + }) + const handleSubmit = (values: any) => { + console.log(values) + } + return ( + <> + +

Create Meeting

+ + {({ values, errors, touched, isSubmitting }) => ( +
+
+ + +
+
+ + +
+ +
+ )} +
+ + ) + } + + export default CreateMeeting \ No newline at end of file From 102defd72cbe10ff026a50382b6bc11dc52435db Mon Sep 17 00:00:00 2001 From: Grayson Marie Smith Date: Sun, 27 Oct 2024 14:14:34 -0500 Subject: [PATCH 02/27] update for specific fields to create and add meeting notes --- app/src/pages/CreateMeeting.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/src/pages/CreateMeeting.tsx b/app/src/pages/CreateMeeting.tsx index 10f3ff3..59d42b4 100644 --- a/app/src/pages/CreateMeeting.tsx +++ b/app/src/pages/CreateMeeting.tsx @@ -5,12 +5,12 @@ import * as Yup from "yup" const CreateMeeting = () => { const initialValues = { - name: "", - description: "", + meeting: "", + notes: "", } const validationSchema = Yup.object().shape({ - name: Yup.string().required("Name is required"), - description: Yup.string().required("Description is required"), + name: Yup.string().required("Meeting is required"), + description: Yup.string().required("Notes are required"), }) const handleSubmit = (values: any) => { console.log(values) @@ -27,20 +27,20 @@ const CreateMeeting = () => { {({ values, errors, touched, isSubmitting }) => (
- +
- +
From dd2fd3a41ee504f96b08b03910f4e2e680a582e2 Mon Sep 17 00:00:00 2001 From: Grayson Marie Smith Date: Sun, 27 Oct 2024 14:27:20 -0500 Subject: [PATCH 03/27] fix routing to create meeting page --- app/src/App.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/App.tsx b/app/src/App.tsx index 0b32a50..839c179 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -3,6 +3,7 @@ import { BrowserRouter as Router, Routes, Route } from "react-router-dom" import Home from "./pages/Home" import MentorDashboard from "./pages/MentorDashboard" import CreateWorkshop from "./pages/CreateWorkshop" +import CreateMeeting from "./pages/CreateMeeting" function App(): ReactElement { return (
@@ -12,6 +13,7 @@ function App(): ReactElement { } /> } /> } /> + } />
From 8eb55b9d3763487e4590bccff9294d791485ac4e Mon Sep 17 00:00:00 2001 From: Lolita Rozenbaum Date: Sun, 27 Oct 2024 16:26:48 -0500 Subject: [PATCH 04/27] dist ignored --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index d77fc52..8145d0e 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,8 @@ .env.test.local .env.production.local +/api/dist + npm-debug.log* yarn-debug.log* yarn-error.log* From d01752c884b7f15f457f72332942a85fd6fd6137 Mon Sep 17 00:00:00 2001 From: charlotteconze <85513920+charlotteconze@users.noreply.github.com> Date: Sun, 27 Oct 2024 16:35:38 -0500 Subject: [PATCH 05/27] fixing backend stuff --- api/package-lock.json | 311 +++++++++++++++++++++++++++++++++++++++++ api/package.json | 8 +- api/server.js | 13 -- api/src/routes/user.ts | 27 +--- api/src/server.ts | 13 ++ 5 files changed, 332 insertions(+), 40 deletions(-) delete mode 100644 api/server.js create mode 100644 api/src/server.ts diff --git a/api/package-lock.json b/api/package-lock.json index 27eabde..69cedcd 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -9,11 +9,13 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "axios": "^1.7.7", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.21.1", "mongodb": "^6.9.0", "mongoose": "^8.4.1", + "nodemon": "^3.1.7", "ts-node-dev": "^2.0.0", "uuid": "^10.0.0" }, @@ -277,6 +279,21 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -398,6 +415,17 @@ "fsevents": "~2.3.2" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -476,6 +504,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -634,6 +670,38 @@ "node": ">= 0.8" } }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -736,6 +804,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -806,6 +882,11 @@ "node": ">=0.10.0" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1161,6 +1242,54 @@ "node": ">= 0.6" } }, + "node_modules/nodemon": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", + "integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/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/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1272,6 +1401,16 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -1379,6 +1518,17 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", @@ -1472,6 +1622,17 @@ "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==" }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -1521,6 +1682,17 @@ "node": ">=0.10.0" } }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -1551,6 +1723,14 @@ "node": ">=0.6" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/tr46": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", @@ -1680,6 +1860,11 @@ "node": ">=14.17" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" + }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", @@ -1999,6 +2184,21 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "requires": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2087,6 +2287,14 @@ "readdirp": "~3.6.0" } }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2147,6 +2355,11 @@ "gopd": "^1.0.1" } }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -2268,6 +2481,21 @@ "unpipe": "~1.0.0" } }, + "follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==" + }, + "form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -2335,6 +2563,11 @@ "get-intrinsic": "^1.1.3" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + }, "has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -2381,6 +2614,11 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2588,6 +2826,38 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, + "nodemon": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", + "integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==", + "requires": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "dependencies": { + "debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -2659,6 +2929,16 @@ "ipaddr.js": "1.9.1" } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" + }, "punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2724,6 +3004,11 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==" + }, "send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", @@ -2801,6 +3086,14 @@ "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==" }, + "simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "requires": { + "semver": "^7.5.3" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -2838,6 +3131,14 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==" }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, "supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -2856,6 +3157,11 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, + "touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==" + }, "tr46": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", @@ -2931,6 +3237,11 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==" }, + "undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" + }, "undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", diff --git a/api/package.json b/api/package.json index b7bee2d..f5156f4 100644 --- a/api/package.json +++ b/api/package.json @@ -2,11 +2,11 @@ "name": "api", "version": "1.0.0", "description": "", - "main": "src/index.js", + "main": "src/index.ts", "scripts": { "build": "tsc", - "start": "node dist/index.js", - "dev": "ts-node-dev --respawn src/index.ts", + "start": "node src/index.ts", + "dev": "nodemon ./src/server.ts", "test": "echo \"Error: no test specified\" && exit 1", "prettier": "prettier --single-quote --write 'src/**/*.{js,ts}'", "format": "prettier --check" @@ -15,11 +15,13 @@ "author": "", "license": "ISC", "dependencies": { + "axios": "^1.7.7", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.21.1", "mongodb": "^6.9.0", "mongoose": "^8.4.1", + "nodemon": "^3.1.7", "ts-node-dev": "^2.0.0", "uuid": "^10.0.0" }, diff --git a/api/server.js b/api/server.js deleted file mode 100644 index 95346f1..0000000 --- a/api/server.js +++ /dev/null @@ -1,13 +0,0 @@ -const mongoose = require('mongoose') - -const dbConnect = async () => { - try { - await mongoose.connect("mongodb+srv://User2:kanersdoghouse@bare-bones.pxrwg.mongodb.net/?retryWrites=true&w=majority&appName=Bare-Bones", { - }) - console.log('MongoDB Connected') - } catch (error) { - console.error(error) - } - } - - dbConnect() \ No newline at end of file diff --git a/api/src/routes/user.ts b/api/src/routes/user.ts index 979d329..5ef6cc8 100644 --- a/api/src/routes/user.ts +++ b/api/src/routes/user.ts @@ -3,31 +3,10 @@ import express from "express" const router = express.Router() const axios = require("axios").default -router.post("/create-group", async (req: any, res: any) => { - const { groupName, groupMembers } = req.body +router.post("/test", async (req: any, res: any) => { + console.log("Received group data:") - console.log("Received group data:", req.body) - - // Validate request body - if (!groupName || !groupMembers) { - return res.status(400).json({ - message: "Invalid data. Please provide group name and group members.", - }) - } - - // Log the received data for debugging - console.log("Received group data:", { - groupName, - groupMembers, - }) - - // Create a new group - const response = await axios.post("http://localhost:3001/api/group", { - groupName, - groupMembers, - }) - - return res.status(response.status).json(response.data) + return res.status(200).json({}) }) export default router diff --git a/api/src/server.ts b/api/src/server.ts new file mode 100644 index 0000000..192da24 --- /dev/null +++ b/api/src/server.ts @@ -0,0 +1,13 @@ +import express from "express" +import connectDB from "./config/db" + +import * as routes from "./routes/index" + +var cors = require("cors") + +const app = express() +app.use(cors()) + +app.use("/user", routes.user) + +app.listen(process.env.PORT || 8000, () => console.log("Server running...")) From da2637f7ef898187e6a1a7ac033464b0e76c369e Mon Sep 17 00:00:00 2001 From: Grayson Marie Smith Date: Sun, 27 Oct 2024 16:53:21 -0500 Subject: [PATCH 06/27] add error messages for form inputs --- app/src/pages/CreateMeeting.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/src/pages/CreateMeeting.tsx b/app/src/pages/CreateMeeting.tsx index 59d42b4..0854af2 100644 --- a/app/src/pages/CreateMeeting.tsx +++ b/app/src/pages/CreateMeeting.tsx @@ -9,8 +9,8 @@ const CreateMeeting = () => { notes: "", } const validationSchema = Yup.object().shape({ - name: Yup.string().required("Meeting is required"), - description: Yup.string().required("Notes are required"), + meeting: Yup.string().required("Please enter a meeting name"), + notes: Yup.string().required("Please enter meeting notes"), }) const handleSubmit = (values: any) => { console.log(values) @@ -27,13 +27,16 @@ const CreateMeeting = () => { {({ values, errors, touched, isSubmitting }) => (
- + + {errors.meeting && touched.meeting && ( +
{errors.meeting}
+ )}
@@ -43,6 +46,9 @@ const CreateMeeting = () => { placeholder="Notes" className="Form-input-box" /> + {errors.notes && touched.notes && ( +
{errors.notes}
+ )}
+ + {errorMessage && ( +
{errorMessage}
+ )} + + {success && ( +
+ Expense logged successfully! +
+ )} + + )} + + } + /> + )}

Create Workshop

{ )} +
{ + setIsModal(true) + }} + className="Button Button-color--dark-1000 Margin-top--10" + > + Add New Files +
) } From f467895da563f1ac46ebc0824f4532a5945d7e17 Mon Sep 17 00:00:00 2001 From: Lolita Rozenbaum Date: Mon, 28 Oct 2024 10:30:30 -0500 Subject: [PATCH 09/27] fixed postman issues --- api/package-lock.json | 2 ++ api/package.json | 1 + api/src/routes/user.ts | 3 ++- api/src/server.ts | 3 +++ 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/api/package-lock.json b/api/package-lock.json index 69cedcd..2078384 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -24,6 +24,7 @@ "@types/express": "^4.17.21", "@types/node": "^22.5.4", "@types/uuid": "^10.0.0", + "body-parser": "^1.20.3", "prettier": "^3.3.3", "typescript": "^5.6.2" } @@ -314,6 +315,7 @@ "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", diff --git a/api/package.json b/api/package.json index f5156f4..e7d9129 100644 --- a/api/package.json +++ b/api/package.json @@ -30,6 +30,7 @@ "@types/express": "^4.17.21", "@types/node": "^22.5.4", "@types/uuid": "^10.0.0", + "body-parser": "^1.20.3", "prettier": "^3.3.3", "typescript": "^5.6.2" } diff --git a/api/src/routes/user.ts b/api/src/routes/user.ts index 5ef6cc8..b482219 100644 --- a/api/src/routes/user.ts +++ b/api/src/routes/user.ts @@ -5,8 +5,9 @@ const axios = require("axios").default router.post("/test", async (req: any, res: any) => { console.log("Received group data:") + const {name} = req.body; - return res.status(200).json({}) + return res.status(200).json(name) }) export default router diff --git a/api/src/server.ts b/api/src/server.ts index 192da24..7543d60 100644 --- a/api/src/server.ts +++ b/api/src/server.ts @@ -1,4 +1,5 @@ import express from "express" +import bodyParser from "body-parser" import connectDB from "./config/db" import * as routes from "./routes/index" @@ -7,6 +8,8 @@ var cors = require("cors") const app = express() app.use(cors()) +app.use(bodyParser.json()) +app.use(bodyParser.urlencoded({ extended: true })) app.use("/user", routes.user) From 901ce30f26816a86a39d698f0127a7b8a209d4a8 Mon Sep 17 00:00:00 2001 From: Lolita Rozenbaum Date: Mon, 28 Oct 2024 10:32:02 -0500 Subject: [PATCH 10/27] ignore env --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8145d0e..e35094d 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ # misc .DS_Store +.env .env.local .env.development.local .env.test.local From 7dd384f87dff8efb0cbf61c819e7ff687b643212 Mon Sep 17 00:00:00 2001 From: kjgilder Date: Mon, 28 Oct 2024 10:49:24 -0500 Subject: [PATCH 11/27] Update 2 for mongo user endpoint --- .gitignore | 1 + api/.env | 2 +- api/package-lock.json | 313 +---------------------------------------- api/src/routes/user.ts | 8 +- 4 files changed, 7 insertions(+), 317 deletions(-) diff --git a/.gitignore b/.gitignore index 8145d0e..e35094d 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ # misc .DS_Store +.env .env.local .env.development.local .env.test.local diff --git a/api/.env b/api/.env index d32fc87..7383386 100644 --- a/api/.env +++ b/api/.env @@ -1 +1 @@ -MONGO_URI=mongodb+srv://User2:kanersdoghouse@bare-bones.pxrwg.mongodb.net/FlatFairDB?retryWrites=true&w=majority&appName=Bare-Bones +MONGO_URI=mongodb+srv://kjgilder:2htuyRZ09zyS1m0R@pwwusers.8ssyv.mongodb.net/?retryWrites=true&w=majority&appName=PWWUsers diff --git a/api/package-lock.json b/api/package-lock.json index 69cedcd..85b4154 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -9,13 +9,11 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "axios": "^1.7.7", "cors": "^2.8.5", "dotenv": "^16.4.5", - "express": "^4.21.1", + "express": "^4.21.0", "mongodb": "^6.9.0", "mongoose": "^8.4.1", - "nodemon": "^3.1.7", "ts-node-dev": "^2.0.0", "uuid": "^10.0.0" }, @@ -279,21 +277,6 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/axios": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", - "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -415,17 +398,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -504,14 +476,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -670,38 +634,6 @@ "node": ">= 0.8" } }, - "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -804,14 +736,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" - } - }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -882,11 +806,6 @@ "node": ">=0.10.0" } }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1242,54 +1161,6 @@ "node": ">= 0.6" } }, - "node_modules/nodemon": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", - "integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==", - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^4", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^7.5.3", - "simple-update-notifier": "^2.0.0", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/nodemon/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/nodemon/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/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1401,16 +1272,6 @@ "node": ">= 0.10" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -1518,17 +1379,6 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", @@ -1622,17 +1472,6 @@ "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==" }, - "node_modules/simple-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -1682,17 +1521,6 @@ "node": ">=0.10.0" } }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -1723,14 +1551,6 @@ "node": ">=0.6" } }, - "node_modules/touch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, "node_modules/tr46": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", @@ -1860,11 +1680,6 @@ "node": ">=14.17" } }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" - }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", @@ -2184,21 +1999,6 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "axios": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", - "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", - "requires": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2287,14 +2087,6 @@ "readdirp": "~3.6.0" } }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2355,11 +2147,6 @@ "gopd": "^1.0.1" } }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -2481,21 +2268,6 @@ "unpipe": "~1.0.0" } }, - "follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==" - }, - "form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -2563,11 +2335,6 @@ "get-intrinsic": "^1.1.3" } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" - }, "has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -2614,11 +2381,6 @@ "safer-buffer": ">= 2.1.2 < 3" } }, - "ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" - }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2826,38 +2588,6 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, - "nodemon": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", - "integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==", - "requires": { - "chokidar": "^3.5.2", - "debug": "^4", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^7.5.3", - "simple-update-notifier": "^2.0.0", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "dependencies": { - "debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "requires": { - "ms": "^2.1.3" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - } - } - }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -2929,16 +2659,6 @@ "ipaddr.js": "1.9.1" } }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, - "pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" - }, "punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3004,11 +2724,6 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==" - }, "send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", @@ -3086,14 +2801,6 @@ "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==" }, - "simple-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", - "requires": { - "semver": "^7.5.3" - } - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -3131,14 +2838,6 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==" }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, "supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -3157,11 +2856,6 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, - "touch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==" - }, "tr46": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", @@ -3237,11 +2931,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==" }, - "undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" - }, "undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", diff --git a/api/src/routes/user.ts b/api/src/routes/user.ts index d941d6d..3114e60 100644 --- a/api/src/routes/user.ts +++ b/api/src/routes/user.ts @@ -20,14 +20,14 @@ const userSchema = new mongoose.Schema({ menteeInfo: [String], // For mentors only meetingSchedule: [String], // For mentees only mentorData: String, // For mentees only - pairings: [[String]] // For admins only, array of pairs of strings + //pairings: [[String]] // For admins only, array of pairs of strings }) const User = mongoose.model("User", userSchema) // Route to create a new user router.post("/create-user", async (req: any, res: any) => { - const { firstName, lastName, username, email, role, workshopIDs, menteeInfo, meetingSchedule, mentorData, pairings } = req.body; + const { firstName, lastName, username, email, role, workshopIDs, menteeInfo, meetingSchedule, mentorData } = req.body; if (!firstName || !lastName || !username || !email || !role) { return res.status(400).json({ message: "Missing required fields" }); @@ -44,14 +44,14 @@ router.post("/create-user", async (req: any, res: any) => { menteeInfo: role === "mentor" ? menteeInfo : undefined, meetingSchedule: role === "mentee" ? meetingSchedule : undefined, mentorData: role === "mentee" ? mentorData : undefined, - pairings: role === "admin" ? pairings : undefined + //pairings: role === "admin" ? pairings : undefined }) try { const savedUser = await newUser.save(); res.status(201).json({ message: "User created successfully", user: savedUser }); } catch (error) { - res.status(500).json({ message: "Failed to create user", error }); + res.status(400).json({ message: "Failed to create user", error }); } }) From 8402e02ace7d84a376a3db32e9214647555c04e0 Mon Sep 17 00:00:00 2001 From: kjgilder Date: Mon, 28 Oct 2024 12:54:15 -0500 Subject: [PATCH 12/27] Added a workshop endpoint --- api/src/routes/index.ts | 3 ++- api/src/routes/workshopid.ts | 42 ++++++++++++++++++++++++++++++++++++ api/src/server.ts | 1 + 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 api/src/routes/workshopid.ts diff --git a/api/src/routes/index.ts b/api/src/routes/index.ts index f30ccaa..ded019c 100644 --- a/api/src/routes/index.ts +++ b/api/src/routes/index.ts @@ -1,9 +1,10 @@ import user from "./user" +import workshopid from "./workshopid" // import workshops from "./workshops" // export { workshops } -export { user } +export { user, workshopid } //import express, { Request, Response, Application } from "express" // import cors from "cors" diff --git a/api/src/routes/workshopid.ts b/api/src/routes/workshopid.ts new file mode 100644 index 0000000..3788a87 --- /dev/null +++ b/api/src/routes/workshopid.ts @@ -0,0 +1,42 @@ +import express from "express" +import mongoose from "mongoose" + +const router = express.Router() + +// MongoDB connection +const mongoURI = "mongodb+srv://kjgilder:2htuyRZ09zyS1m0R@pwwusers.8ssyv.mongodb.net/?retryWrites=true&w=majority&appName=PWWUsers" +mongoose.connect(mongoURI) + .then(() => console.log("Connected to MongoDB")) + .catch((error) => console.error("Failed to connect to MongoDB:", error)) + +// Workshop schema definition (name and S3 bucket ID) +const workshopIDSchema = new mongoose.Schema({ + name: String, + s3ID: String +}) + +const WorkshopID = mongoose.model("WorkshopID", workshopIDSchema) + +// Route to create a new workshop +router.post("/create-workshopID", async (req: any, res: any) => { + const { name, s3ID } = req.body; + + if (!name || !s3ID ) { + return res.status(400).json({ message: "Missing required fields" }); + } + + // Create a new workshop + const newWorkshop = new WorkshopID({ + name, + s3ID + }) + + try { + const savedWorkshop = await newWorkshop.save(); + res.status(201).json({ message: "Workshop created successfully", WorkshopID: savedWorkshop }); + } catch (error) { + res.status(400).json({ message: "Failed to create workshop", error }); + } +}) + +export default router \ No newline at end of file diff --git a/api/src/server.ts b/api/src/server.ts index 7543d60..0188b10 100644 --- a/api/src/server.ts +++ b/api/src/server.ts @@ -12,5 +12,6 @@ app.use(bodyParser.json()) app.use(bodyParser.urlencoded({ extended: true })) app.use("/user", routes.user) +app.use("/workshopid", routes.workshopid) app.listen(process.env.PORT || 8000, () => console.log("Server running...")) From f98a897a678faea0c4c5799282cef35b1c58e115 Mon Sep 17 00:00:00 2001 From: txingxie Date: Sat, 2 Nov 2024 19:05:29 -0500 Subject: [PATCH 13/27] workshop endpoint w/ populate and no populate versions, index.ts boilerplate --- api/src/controllers/workshopController.ts | 77 +++++++++++++++++++++++ api/src/model/Workshop.ts | 30 +++++++++ api/src/routes/index.ts | 7 +++ api/src/routes/workshop.ts | 26 ++++++++ 4 files changed, 140 insertions(+) create mode 100644 api/src/controllers/workshopController.ts create mode 100644 api/src/model/Workshop.ts create mode 100644 api/src/routes/workshop.ts diff --git a/api/src/controllers/workshopController.ts b/api/src/controllers/workshopController.ts new file mode 100644 index 0000000..248c831 --- /dev/null +++ b/api/src/controllers/workshopController.ts @@ -0,0 +1,77 @@ +// NO POPULATE VERSION + +import { Request, Response } from 'express'; +import { Workshop } from '../model/Workshop'; + +export const createWorkshop = async (req: Request, res: Response) => { + try { + const { mentorId, menteeId, textContent } = req.body; + + const newWorkshop = new Workshop({ + mentor: mentorId, + mentee: menteeId, + textContent, + }); + + const savedWorkshop = await newWorkshop.save(); + res.status(201).json(savedWorkshop); + } catch (error) { + res.status(500).json({ message: 'Error creating workshop', error }); + } +}; + +export const getWorkshop = async (req: Request, res: Response) => { + try { + const { id } = req.params; + + const workshop = await Workshop.findById(id); + + if (!workshop) { + return res.status(404).json({ message: 'Workshop not found' }); + } + + res.status(200).json(workshop); + } catch (error) { + res.status(500).json({ message: 'Error retrieving workshop', error }); + } +}; + +// POPULATE VERSION (if details of mentor/mentee objects are needed on the frontend like name or picture) + +// import { Request, Response } from 'express'; +// import { Workshop } from '../model/Workshop'; + +// export const createWorkshop = async (req: Request, res: Response) => { +// try { +// const { mentorId, menteeId, textContent } = req.body; + +// const newWorkshop = new Workshop({ +// mentor: mentorId, +// mentee: menteeId, +// textContent, +// }); + +// const savedWorkshop = await newWorkshop.save(); +// res.status(201).json(savedWorkshop); +// } catch (error) { +// res.status(500).json({ message: 'Error creating workshop', error }); +// } +// }; + +// export const getWorkshop = async (req: Request, res: Response) => { +// try { +// const { id } = req.params; + +// const workshop = await Workshop.findById(id) +// .populate('mentor') // Populate full user details for mentor +// .populate('mentee'); // Populate full user details for mentee + +// if (!workshop) { +// return res.status(404).json({ message: 'Workshop not found' }); +// } + +// res.status(200).json(workshop); +// } catch (error) { +// res.status(500).json({ message: 'Error retrieving workshop', error }); +// } +// }; diff --git a/api/src/model/Workshop.ts b/api/src/model/Workshop.ts new file mode 100644 index 0000000..3d17871 --- /dev/null +++ b/api/src/model/Workshop.ts @@ -0,0 +1,30 @@ +import mongoose, { Schema, Document } from 'mongoose'; +import { IUser } from './User'; + +// interface +interface IWorkshop extends Document { + mentor: Schema.Types.ObjectId | IUser; + mentee: Schema.Types.ObjectId | IUser; + textContent: string; + createdAt: Date; + + updateContent(newContent: string): Promise; +} + +// workshop schema +const WorkshopSchema: Schema = new Schema({ + mentor: { type: Schema.Types.ObjectId, ref: 'User', required: true }, + mentee: { type: Schema.Types.ObjectId, ref: 'User', required: true }, + textContent: { type: String, required: true }, + createdAt: { type: Date, default: Date.now } +}); + +// update text content of the workshop +WorkshopSchema.methods.updateContent = async function (newContent: string): Promise { + this.textContent = newContent; + await this.save(); +}; + +// mongoose model +const Workshop = mongoose.model('Workshop', WorkshopSchema); +export { Workshop, IWorkshop }; diff --git a/api/src/routes/index.ts b/api/src/routes/index.ts index b6bf883..8520ab4 100644 --- a/api/src/routes/index.ts +++ b/api/src/routes/index.ts @@ -1,7 +1,14 @@ import user from "./user" +import express from 'express'; +import workshopRoutes from './workshop'; // Import workshop routes export { user } +const router = express.Router(); +router.use('/workshops', workshopRoutes); // Use workshop routes + +export default router; + // import express, { Request, Response, Application } from "express" // import cors from "cors" // import dbConnect from "./config/dbConnect" diff --git a/api/src/routes/workshop.ts b/api/src/routes/workshop.ts new file mode 100644 index 0000000..dbe6f3a --- /dev/null +++ b/api/src/routes/workshop.ts @@ -0,0 +1,26 @@ +// NO POPULATE VERSION + +import express from 'express'; +import { createWorkshop, getWorkshop } from '../controllers/workshopController'; + +const router = express.Router(); + +router.post('/workshops', createWorkshop); +// router.get('/workshops/:id', getWorkshop); +router.get('/workshops/:id', async (req: express.Request, res: express.Response) => { + await getWorkshop(req, res); +}); + +export default router; + +// POPULATE VERSION (if details of mentor/mentee objects are needed on the frontend like name or picture) + +// import express from 'express'; +// import { createWorkshop, getWorkshop } from '../controllers/workshopController'; + +// const router = express.Router(); + +// router.post('/workshops', createWorkshop); +// router.get('/workshops/:id', getWorkshop); + +// export default router; From 3aa447368d181ed8a1ba820bc69d5d7e284cc105 Mon Sep 17 00:00:00 2001 From: Samuel Thompson Date: Sat, 2 Nov 2024 19:41:56 -0500 Subject: [PATCH 14/27] Update CreateWorkshop.tsx --- app/src/pages/CreateWorkshop.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/pages/CreateWorkshop.tsx b/app/src/pages/CreateWorkshop.tsx index 3c43c9c..ca02a6b 100644 --- a/app/src/pages/CreateWorkshop.tsx +++ b/app/src/pages/CreateWorkshop.tsx @@ -26,7 +26,7 @@ const CreateWorkshop = () => { file: "", } const validationSchema2 = Yup.object().shape({ - item: Yup.string().required("Please enter an item or activity"), + file: Yup.mixed().required("Please select a file") }) const handleSubmit2 = async (values: any, { resetForm }: any) => { @@ -100,7 +100,7 @@ const CreateWorkshop = () => { {success && (
- Expense logged successfully! + File uploaded successfully!
)} From 35eb0b9c5d85660321ccb22d9a8164542d72e138 Mon Sep 17 00:00:00 2001 From: Samuel Thompson Date: Sat, 2 Nov 2024 19:52:26 -0500 Subject: [PATCH 15/27] Add title and description box to file upload --- app/src/pages/CreateWorkshop.tsx | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/app/src/pages/CreateWorkshop.tsx b/app/src/pages/CreateWorkshop.tsx index ca02a6b..a98ffed 100644 --- a/app/src/pages/CreateWorkshop.tsx +++ b/app/src/pages/CreateWorkshop.tsx @@ -23,9 +23,13 @@ const CreateWorkshop = () => { const [errorMessage, setErrorMessage] = useState("") const initialValues2 = { + title: "", + desc: "", file: "", } const validationSchema2 = Yup.object().shape({ + title: Yup.string().required("Title is required"), + desc: Yup.string().required("Description is required"), file: Yup.mixed().required("Please select a file") }) @@ -64,6 +68,32 @@ const CreateWorkshop = () => { > {({ values, errors, touched, isSubmitting }) => (
+
+ + + {errors.title && touched.title && ( +
{errors.title}
+ )} +
+
+ + + {errors.desc && touched.desc && ( +
{errors.desc}
+ )} +
Date: Sun, 3 Nov 2024 14:54:28 -0600 Subject: [PATCH 16/27] Making workshop changes --- app/src/pages/CreateWorkshop.tsx | 129 +++++++++++++++++-------------- 1 file changed, 73 insertions(+), 56 deletions(-) diff --git a/app/src/pages/CreateWorkshop.tsx b/app/src/pages/CreateWorkshop.tsx index 04c565c..3e02e76 100644 --- a/app/src/pages/CreateWorkshop.tsx +++ b/app/src/pages/CreateWorkshop.tsx @@ -1,60 +1,77 @@ -import React, { useState } from "react" -import { Formik, Form, Field, FieldArray } from "formik" -import Navbar from "../components/Navbar" +import React from "react" +import { Formik, Form, Field } from "formik" import * as Yup from "yup" +import Navbar from "../components/Navbar" const CreateWorkshop = () => { - const initialValues = { - name: "", - description: "", - } - const validationSchema = Yup.object().shape({ - name: Yup.string().required("Name is required"), - description: Yup.string().required("Description is required"), - }) - const handleSubmit = (values: any) => { - console.log(values) - } - return ( - <> - -

Create Workshop

- - {({ values, errors, touched, isSubmitting }) => ( - -
- - -
-
- - -
- - - )} -
- - ) + // Initial form values + const initialValues = { + name: "", + description: "", + } + + // Validation schema using Yup + const validationSchema = Yup.object().shape({ + name: Yup.string().required("Name is required"), + description: Yup.string().required("Description is required"), + }) + + // Handle form submission + const handleSubmit = (values: any) => { + console.log(values) } - - export default CreateWorkshop \ No newline at end of file + + return ( + <> + +

Create Workshop

+ + {({ errors, touched, isSubmitting }) => ( +
+
+ + + {/* Display error message if name field is invalid */} + {errors.name && touched.name && ( +
{errors.name}
+ )} +
+ +
+ + + {/* Display error message if description field is invalid */} + {errors.description && touched.description && ( +
{errors.description}
+ )} +
+ + +
+ )} +
+ + ) +} + +export default CreateWorkshop From 29b041d033319a8239a00ea0322ab65c1aca3ac8 Mon Sep 17 00:00:00 2001 From: charlotteconze <85513920+charlotteconze@users.noreply.github.com> Date: Sun, 3 Nov 2024 15:03:54 -0600 Subject: [PATCH 17/27] Removing flatfair stuff --- api/src/model/Expense.ts | 69 ---------------------------------- api/src/model/Group.ts | 80 ---------------------------------------- api/src/model/User.ts | 35 ------------------ 3 files changed, 184 deletions(-) delete mode 100644 api/src/model/Expense.ts delete mode 100644 api/src/model/Group.ts delete mode 100644 api/src/model/User.ts diff --git a/api/src/model/Expense.ts b/api/src/model/Expense.ts deleted file mode 100644 index cc88215..0000000 --- a/api/src/model/Expense.ts +++ /dev/null @@ -1,69 +0,0 @@ -import mongoose, { Schema, Document } from 'mongoose'; -import { IUser } from './User'; // Import User interface -import { IGroup } from './Group'; // Import Group interface - -// Interface for the Expense document -interface IExpense extends Document { - _id: Schema.Types.ObjectId; - amount: number; - description: string; - category: string; - group: (Schema.Types.ObjectId | IGroup); - status: string; - receipt: File; - date: Date; - createdBy: (Schema.Types.ObjectId | IUser); - allocatedTo: Map<(Schema.Types.ObjectId | IUser), number>; - - addReceipt(receipt: File): Promise; - editExpense(newAmount: number, newDescription: string): Promise; - deleteExpense(): Promise; - allocateExpense(portions: Map): Promise; -} - -// Mongoose Expense Schema -const expenseSchema: Schema = new Schema({ - // expenseID: { type: String, required: true }, - amount: { type: Number, required: true }, - description: { type: String, required: true }, - category: { type: String, required: true }, - group: { type: Schema.Types.ObjectId, ref: 'Group', required: true }, // Reference to Group - status: { type: String, required: true }, // e.g., 'pending', 'settled' - receipt: { type: Buffer }, // File storage for the receipt (you can handle file uploads in your app) - date: { type: Date, default: Date.now }, - createdBy: { type: Schema.Types.ObjectId, ref: 'User', required: true }, // Reference to User - allocatedTo: { - type: Map, - of: Number, // A map of User ObjectId and allocation amount - required: true - } -}); - -// Method to add a receipt to the expense -expenseSchema.methods.addReceipt = async function (receipt: File): Promise { - this.receipt = receipt; - await this.save(); -}; - -// Method to edit the amount and description of the expense -expenseSchema.methods.editExpense = async function (newAmount: number, newDescription: string): Promise { - this.amount = newAmount; - this.description = newDescription; - await this.save(); -}; - -// Method to delete the expense -expenseSchema.methods.deleteExpense = async function (): Promise { - await this.remove(); // Removes the document from the database -}; - -// Method to allocate portions of the expense to users -expenseSchema.methods.allocateExpense = async function (portions: Map): Promise { - this.allocatedTo = portions; // Overwrite the existing allocations - await this.save(); -}; - -// Mongoose Expense Model -const Expense = mongoose.model('Expense', expenseSchema); - -export { Expense, IExpense }; diff --git a/api/src/model/Group.ts b/api/src/model/Group.ts deleted file mode 100644 index 4d6ddc6..0000000 --- a/api/src/model/Group.ts +++ /dev/null @@ -1,80 +0,0 @@ -import mongoose, { Schema, Document } from 'mongoose'; -import { User, IUser } from './User'; // Import User interface -import { IExpense } from './Expense'; // Import Expense interface - -// Interface for the Group document -interface IGroup extends Document { - _id: Schema.Types.ObjectId; - groupName: string; - groupDescription: string; - members: (Schema.Types.ObjectId | IUser)[]; - expenses: (Schema.Types.ObjectId | IExpense)[]; - leader: (Schema.Types.ObjectId | IUser); - - addMember(user: IUser): Promise; - removeMember(userID: string): Promise; - notifyNewExpense(expense: IExpense): Promise; - viewGroupBalance(): Promise; - viewSettledAndUnsettledExpenses(): Promise<{ settled: IExpense[]; unsettled: IExpense[] }>; -} - -// Mongoose Group Schema -const groupSchema: Schema = new Schema({ - // groupID: { type: String, default: uuid.v4 }, - groupName: { type: String, required: true }, - groupDescription: { type: String }, - members: [{ type: Schema.Types.ObjectId, ref: User.modelName }], // List of User references - expenses: [{ type: Schema.Types.ObjectId, ref: 'Expense' }], // List of Expense references - leader: { type: Schema.Types.ObjectId, ref: User.modelName, required: true } // Leader of the group -}); - -// Method to add a member to the group -groupSchema.methods.addMember = async function (user: IUser): Promise { - if (!this.members.includes(user._id)) { - this.members.push(user._id); // Add user to the members array - await this.save(); // Persist changes - } -}; - -// Method to remove a member from the group by userID -groupSchema.methods.removeMember = async function (userID: Schema.Types.ObjectId): Promise { - this.members = this.members.filter((member: IUser) => member._id !== userID); - await this.save(); // Persist changes -}; - -// Method to notify members of a new expense -// NEEDS TO NOTIFY MEMBERS -groupSchema.methods.notifyNewExpense = async function (expense: IExpense): Promise { - this.expenses.push(expense._id); // Add the new expense to the expenses array - await this.save(); // Persist changes - // Here you would trigger any notification system if needed -}; - -// // Method to view the total group balance -// groupSchema.methods.viewGroupBalance = async function (): Promise { -// let totalBalance = 0; -// for (const expense of this.expenses) { -// totalBalance += expense.amount; // Assuming 'amount' is a field in IExpense -// } -// return totalBalance; -// }; - -// // Method to view settled and unsettled expenses -// groupSchema.methods.viewSettledAndUnsettledExpenses = async function (): Promise<{ settled: IExpense[], unsettled: IExpense[] }> { -// const settled: IExpense[] = []; -// const unsettled: IExpense[] = []; - -// for (const expense of this.expenses) { -// if (expense.isSettled) { // Assuming 'isSettled' is a field in IExpense -// settled.push(expense); -// } else { -// unsettled.push(expense); -// } -// } -// return { settled, unsettled }; -// }; - -// Mongoose Group Model -const Group = mongoose.model('Group', groupSchema); - -export { Group, IGroup }; diff --git a/api/src/model/User.ts b/api/src/model/User.ts deleted file mode 100644 index 46f10f1..0000000 --- a/api/src/model/User.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Schema, model, Document } from 'mongoose'; -import { Group, IGroup } from './Group'; - -// Define the IUser interface -interface IUser extends Document { - _id: Schema.Types.ObjectId; - name: string; - email: string; - password: string; - groups: (Schema.Types.ObjectId | IGroup)[]; - friends: (Schema.Types.ObjectId | IUser)[]; - addFriend: (friendId: string) => Promise; -} - -// Mongoose User Schema -const userSchema: Schema = new Schema({ - // _id: { type: String, default: uuid.v4 }, - name: { type: String, required: true }, - email: { type: String, required: true }, - password: { type: String, required: true }, - groups: [{ type: Schema.Types.ObjectId, ref: 'Group' }], - friends: [{ type: Schema.Types.ObjectId, ref: 'User' }] -}); - -// Instance method to add a friend -userSchema.methods.addFriend = async function (friendId: Schema.Types.ObjectId): Promise { - this.friends.push(friendId); // Modify the friends array - await this.save(); // Save the updated user document -}; - -// Mongoose Model -const User = model('User', userSchema); - -//export User model and IUser interface -export { User, IUser }; \ No newline at end of file From e51654be89db8233d437cdba59c03e157b398e96 Mon Sep 17 00:00:00 2001 From: charlotteconze <85513920+charlotteconze@users.noreply.github.com> Date: Sun, 3 Nov 2024 15:05:41 -0600 Subject: [PATCH 18/27] adding comments --- api/src/model/Workshop.ts | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/api/src/model/Workshop.ts b/api/src/model/Workshop.ts index 3d17871..54a188b 100644 --- a/api/src/model/Workshop.ts +++ b/api/src/model/Workshop.ts @@ -1,30 +1,30 @@ -import mongoose, { Schema, Document } from 'mongoose'; -import { IUser } from './User'; - +import mongoose, { Schema, Document } from "mongoose" // interface interface IWorkshop extends Document { - mentor: Schema.Types.ObjectId | IUser; - mentee: Schema.Types.ObjectId | IUser; - textContent: string; - createdAt: Date; + mentor: Schema.Types.ObjectId // TODO: add mentor type + mentee: Schema.Types.ObjectId // TODO: add mentee type + textContent: string + createdAt: Date - updateContent(newContent: string): Promise; + updateContent(newContent: string): Promise } // workshop schema const WorkshopSchema: Schema = new Schema({ - mentor: { type: Schema.Types.ObjectId, ref: 'User', required: true }, - mentee: { type: Schema.Types.ObjectId, ref: 'User', required: true }, - textContent: { type: String, required: true }, - createdAt: { type: Date, default: Date.now } -}); + mentor: { type: Schema.Types.ObjectId, ref: "User", required: true }, + mentee: { type: Schema.Types.ObjectId, ref: "User", required: true }, + textContent: { type: String, required: true }, + createdAt: { type: Date, default: Date.now }, +}) // update text content of the workshop -WorkshopSchema.methods.updateContent = async function (newContent: string): Promise { - this.textContent = newContent; - await this.save(); -}; +WorkshopSchema.methods.updateContent = async function ( + newContent: string +): Promise { + this.textContent = newContent + await this.save() +} // mongoose model -const Workshop = mongoose.model('Workshop', WorkshopSchema); -export { Workshop, IWorkshop }; +const Workshop = mongoose.model("Workshop", WorkshopSchema) +export { Workshop, IWorkshop } From 7b8bd6eb90187dc9e6487f396a4047b6cfb2bd13 Mon Sep 17 00:00:00 2001 From: charlotteconze <85513920+charlotteconze@users.noreply.github.com> Date: Sun, 3 Nov 2024 15:15:22 -0600 Subject: [PATCH 19/27] Minor tweaks --- api/src/config/db.ts | 23 +++++----- api/src/routes/index.ts | 4 +- api/src/routes/user.ts | 45 ++++++++++++------- api/src/routes/{workshopid.ts => workshop.ts} | 29 ++++++------ api/src/server.ts | 2 + 5 files changed, 57 insertions(+), 46 deletions(-) rename api/src/routes/{workshopid.ts => workshop.ts} (52%) diff --git a/api/src/config/db.ts b/api/src/config/db.ts index 6c21f0e..3e19c51 100644 --- a/api/src/config/db.ts +++ b/api/src/config/db.ts @@ -1,18 +1,17 @@ -import mongoose from 'mongoose' -import dotenv from 'dotenv' -import path from 'path' +import mongoose from "mongoose" +import dotenv from "dotenv" +import path from "path" -dotenv.config({ path: path.resolve(__dirname, '../../.env') }); +dotenv.config({ path: path.resolve(__dirname, "../../.env") }) // Connect to MongoDB const dbConnect = async () => { - try { - await mongoose.connect(process.env.MONGO_URI as string, { - }) - console.log("Connected to MongoDB") - } catch (error) { - console.error(error) - } + try { + await mongoose.connect(process.env.MONGO_URI as string, {}) + console.log("Connected to MongoDB") + } catch (error) { + console.error(error) + } } -export default dbConnect \ No newline at end of file +export default dbConnect diff --git a/api/src/routes/index.ts b/api/src/routes/index.ts index ded019c..056e826 100644 --- a/api/src/routes/index.ts +++ b/api/src/routes/index.ts @@ -1,8 +1,7 @@ import user from "./user" -import workshopid from "./workshopid" +import workshopid from "./workshop" // import workshops from "./workshops" - // export { workshops } export { user, workshopid } @@ -42,4 +41,3 @@ export { user, workshopid } // app.listen(port, () => { // console.log(`Server running at http://localhost:${port}`) // }) - diff --git a/api/src/routes/user.ts b/api/src/routes/user.ts index 7387c43..cbc829b 100644 --- a/api/src/routes/user.ts +++ b/api/src/routes/user.ts @@ -1,13 +1,11 @@ import express from "express" import mongoose from "mongoose" +import dbConnect from "../config/db" const router = express.Router() -// MongoDB connection -const mongoURI = "mongodb+srv://kjgilder:2htuyRZ09zyS1m0R@pwwusers.8ssyv.mongodb.net/?retryWrites=true&w=majority&appName=PWWUsers" -mongoose.connect(mongoURI) - .then(() => console.log("Connected to MongoDB")) - .catch((error) => console.error("Failed to connect to MongoDB:", error)) +// Call the dbConnect function to connect to MongoDB +dbConnect() // User schema definition const userSchema = new mongoose.Schema({ @@ -16,20 +14,30 @@ const userSchema = new mongoose.Schema({ username: String, email: String, role: { type: String, enum: ["mentor", "mentee", "admin"], required: true }, - workshopIDs: [String], // For mentors only - menteeInfo: [String], // For mentors only - meetingSchedule: [String], // For mentees only - mentorData: String, // For mentees only + workshopIDs: [String], // For mentors only + menteeInfo: [String], // For mentors only + meetingSchedule: [String], // For mentees only + mentorData: String, // For mentees only }) const User = mongoose.model("User", userSchema) // Route to create a new user router.post("/create-user", async (req: any, res: any) => { - const { firstName, lastName, username, email, role, workshopIDs, menteeInfo, meetingSchedule, mentorData } = req.body; + const { + firstName, + lastName, + username, + email, + role, + workshopIDs, + menteeInfo, + meetingSchedule, + mentorData, + } = req.body if (!firstName || !lastName || !username || !email || !role) { - return res.status(400).json({ message: "Missing required fields" }); + return res.status(400).json({ message: "Missing required fields" }) } // Create a new user based on role @@ -46,18 +54,21 @@ router.post("/create-user", async (req: any, res: any) => { }) try { - const savedUser = await newUser.save(); - res.status(201).json({ message: "User created successfully", user: savedUser }); + const savedUser = await newUser.save() + res + .status(201) + .json({ message: "User created successfully", user: savedUser }) } catch (error) { - res.status(400).json({ message: "Failed to create user", error }); + res.status(400).json({ message: "Failed to create user", error }) } }) +// Test route to check if the API is working router.post("/test", async (req: any, res: any) => { console.log("Received group data:") - const {name} = req.body; + const { name } = req.body - return res.status(200).json(name) + return res.status(200).json({ name }) }) -export default router \ No newline at end of file +export default router diff --git a/api/src/routes/workshopid.ts b/api/src/routes/workshop.ts similarity index 52% rename from api/src/routes/workshopid.ts rename to api/src/routes/workshop.ts index 3788a87..5e045bb 100644 --- a/api/src/routes/workshopid.ts +++ b/api/src/routes/workshop.ts @@ -1,42 +1,43 @@ import express from "express" import mongoose from "mongoose" +import dbConnect from "../config/db" // Import the dbConnect function const router = express.Router() -// MongoDB connection -const mongoURI = "mongodb+srv://kjgilder:2htuyRZ09zyS1m0R@pwwusers.8ssyv.mongodb.net/?retryWrites=true&w=majority&appName=PWWUsers" -mongoose.connect(mongoURI) - .then(() => console.log("Connected to MongoDB")) - .catch((error) => console.error("Failed to connect to MongoDB:", error)) +// Call the dbConnect function to connect to MongoDB +dbConnect() // Workshop schema definition (name and S3 bucket ID) const workshopIDSchema = new mongoose.Schema({ name: String, - s3ID: String + s3ID: String, }) const WorkshopID = mongoose.model("WorkshopID", workshopIDSchema) // Route to create a new workshop router.post("/create-workshopID", async (req: any, res: any) => { - const { name, s3ID } = req.body; + const { name, s3ID } = req.body - if (!name || !s3ID ) { - return res.status(400).json({ message: "Missing required fields" }); + if (!name || !s3ID) { + return res.status(400).json({ message: "Missing required fields" }) } // Create a new workshop const newWorkshop = new WorkshopID({ name, - s3ID + s3ID, }) try { - const savedWorkshop = await newWorkshop.save(); - res.status(201).json({ message: "Workshop created successfully", WorkshopID: savedWorkshop }); + const savedWorkshop = await newWorkshop.save() + res.status(201).json({ + message: "Workshop created successfully", + WorkshopID: savedWorkshop, + }) } catch (error) { - res.status(400).json({ message: "Failed to create workshop", error }); + res.status(400).json({ message: "Failed to create workshop", error }) } }) -export default router \ No newline at end of file +export default router diff --git a/api/src/server.ts b/api/src/server.ts index 0188b10..a601f88 100644 --- a/api/src/server.ts +++ b/api/src/server.ts @@ -14,4 +14,6 @@ app.use(bodyParser.urlencoded({ extended: true })) app.use("/user", routes.user) app.use("/workshopid", routes.workshopid) +connectDB() + app.listen(process.env.PORT || 8000, () => console.log("Server running...")) From dc8e89f5cf2100638a1d90740bb8f54ca2732c28 Mon Sep 17 00:00:00 2001 From: kjgilder Date: Sun, 3 Nov 2024 16:28:52 -0600 Subject: [PATCH 20/27] Fixed package.json --- api/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/package.json b/api/package.json index cd8bfbd..b54317c 100644 --- a/api/package.json +++ b/api/package.json @@ -2,11 +2,11 @@ "name": "api", "version": "1.0.0", "description": "", - "main": "src/index.js", + "main": "src/index.ts", "scripts": { "build": "tsc", - "start": "node dist/index.js", - "dev": "ts-node-dev --respawn src/routes/index.ts", + "start": "node src/index.ts", + "dev": "nodemon ./src/server.ts", "test": "echo \"Error: no test specified\" && exit 1", "prettier": "prettier --single-quote --write 'src/**/*.{js,ts}'", "format": "prettier --check" From 5d4099f9053f26dc5404aeae1234fbb43ac6145d Mon Sep 17 00:00:00 2001 From: Katelyn Gildersleeve <155999260+kjgilder@users.noreply.github.com> Date: Sun, 3 Nov 2024 17:27:40 -0600 Subject: [PATCH 21/27] Delete api/.env --- api/.env | 1 - 1 file changed, 1 deletion(-) delete mode 100644 api/.env diff --git a/api/.env b/api/.env deleted file mode 100644 index 7383386..0000000 --- a/api/.env +++ /dev/null @@ -1 +0,0 @@ -MONGO_URI=mongodb+srv://kjgilder:2htuyRZ09zyS1m0R@pwwusers.8ssyv.mongodb.net/?retryWrites=true&w=majority&appName=PWWUsers From 95e2c45d8df91a24c530b8b6b2b12c297dc5cb04 Mon Sep 17 00:00:00 2001 From: Samuel Thompson Date: Sun, 3 Nov 2024 17:30:06 -0600 Subject: [PATCH 22/27] Rename file upload functions --- app/src/pages/CreateWorkshop.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/pages/CreateWorkshop.tsx b/app/src/pages/CreateWorkshop.tsx index a98ffed..4bc3539 100644 --- a/app/src/pages/CreateWorkshop.tsx +++ b/app/src/pages/CreateWorkshop.tsx @@ -22,18 +22,18 @@ const CreateWorkshop = () => { const [success, setSuccess] = useState(false) const [errorMessage, setErrorMessage] = useState("") - const initialValues2 = { + const fileUploadInitialValues = { title: "", desc: "", file: "", } - const validationSchema2 = Yup.object().shape({ + const fileValidation = Yup.object().shape({ title: Yup.string().required("Title is required"), desc: Yup.string().required("Description is required"), file: Yup.mixed().required("Please select a file") }) - const handleSubmit2 = async (values: any, { resetForm }: any) => { + const handleFileSumbit = async (values: any, { resetForm }: any) => { setIsLoading(true) setErrorMessage("") // Clear error message at the start of submission @@ -62,9 +62,9 @@ const CreateWorkshop = () => { action={() => setIsModal(false)} body={ {({ values, errors, touched, isSubmitting }) => (
From 47b2543364e3ae306b053e0b10f2fbaaebfc83d6 Mon Sep 17 00:00:00 2001 From: kjgilder Date: Sun, 3 Nov 2024 17:42:58 -0600 Subject: [PATCH 23/27] Fixed workshop create --- api/src/routes/workshop.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/api/src/routes/workshop.ts b/api/src/routes/workshop.ts index e72da65..9d737c2 100644 --- a/api/src/routes/workshop.ts +++ b/api/src/routes/workshop.ts @@ -15,20 +15,20 @@ const workshopIDSchema = new mongoose.Schema({ s3ID: String, }) -const WorkshopID = mongoose.model("WorkshopID", workshopIDSchema) +const Workshop = mongoose.model("WorkshopID", workshopIDSchema) // Route to create a new workshop -router.post("/create-workshopID", async (req: any, res: any) => { - const { name, s3ID } = req.body +router.post("/create-workshop", async (req: any, res: any) => { + const { name, s3id } = req.body - if (!name || !s3ID) { + if (!name || !s3id) { return res.status(400).json({ message: "Missing required fields" }) } // Create a new workshop - const newWorkshop = new WorkshopID({ + const newWorkshop = new Workshop({ name, - s3ID, + s3id, }) try { @@ -38,11 +38,11 @@ router.post("/create-workshopID", async (req: any, res: any) => { WorkshopID: savedWorkshop, }) } catch (error) { - res.status(400).json({ message: "Failed to create workshop", error }) + res.status(401).json({ message: "Failed to create workshop", error }) } }) -router.post("/workshops", createWorkshop) +// router.post("/workshops", createWorkshop) // router.get('/workshops/:id', getWorkshop); router.get( "/workshops/:id", From 7d9c063ab2e9d1aebc10637a936dc3d6b8b80344 Mon Sep 17 00:00:00 2001 From: Samuel Thompson Date: Sun, 3 Nov 2024 17:51:21 -0600 Subject: [PATCH 24/27] Create section to display files uploaded --- app/src/pages/CreateWorkshop.tsx | 51 ++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/app/src/pages/CreateWorkshop.tsx b/app/src/pages/CreateWorkshop.tsx index 4bc3539..b9d40d7 100644 --- a/app/src/pages/CreateWorkshop.tsx +++ b/app/src/pages/CreateWorkshop.tsx @@ -21,6 +21,8 @@ const CreateWorkshop = () => { const [isLoading, setIsLoading] = useState(false) const [success, setSuccess] = useState(false) const [errorMessage, setErrorMessage] = useState("") + const [fileTitles, setFileTitles] = useState([]) + const [fileAdded, setFileAdded] = useState(false) const fileUploadInitialValues = { title: "", @@ -35,22 +37,24 @@ const CreateWorkshop = () => { const handleFileSumbit = async (values: any, { resetForm }: any) => { setIsLoading(true) - setErrorMessage("") // Clear error message at the start of submission + setErrorMessage("") try { - // Package the final data to submit const finalData = { - item: values.item, + title: values.title, + desc: values.desc, + file: values.file, } - - console.log("Submitting data:", finalData) - setSuccess(true) - setErrorMessage("") // Ensure error message is cleared on success - resetForm() + setFileTitles((prevTitles) => [...prevTitles, values.title]); + setFileAdded(true) + console.log("Submitting data:", finalData) + setSuccess(true) + setErrorMessage("") + resetForm() } catch (error) { - console.error("Error submitting:", error) + console.error("Error submitting:", error) } finally { - setIsLoading(false) + setIsLoading(false) } } return ( @@ -166,23 +170,32 @@ const CreateWorkshop = () => { className="Form-input-box" />
+ { fileAdded && (
+

Uploaded Files:

+
    + {fileTitles.map((title, index) => ( +
  • {title}
  • + ))} +
+
+ )} +
{ + setIsModal(true) + }} + className="Button Button-color--dark-1000 Margin-top--10" + > + Add New Files +
)} -
{ - setIsModal(true) - }} - className="Button Button-color--dark-1000 Margin-top--10" - > - Add New Files -
) } From 628b58cbb3d8150e178a2220c9588f0452f09c9c Mon Sep 17 00:00:00 2001 From: Lyton Date: Thu, 7 Nov 2024 14:31:24 -0600 Subject: [PATCH 25/27] fix: check linting issues --- api/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/package.json b/api/package.json index b54317c..a4c2d8f 100644 --- a/api/package.json +++ b/api/package.json @@ -9,7 +9,7 @@ "dev": "nodemon ./src/server.ts", "test": "echo \"Error: no test specified\" && exit 1", "prettier": "prettier --single-quote --write 'src/**/*.{js,ts}'", - "format": "prettier --check" + "format": "prettier --check ." }, "keywords": [], "author": "", @@ -33,4 +33,3 @@ "typescript": "^5.6.2" } } - From 967c655de74b4ef6427ae4d1eae7eea5c07483c1 Mon Sep 17 00:00:00 2001 From: Lyton Date: Sat, 9 Nov 2024 00:57:07 -0600 Subject: [PATCH 26/27] fix: run prettier fix --- .github/workflows/lint.yml | 4 +- README.md | 2 +- api/src/config/db.ts | 18 +- api/src/controllers/workshopController.ts | 66 ++--- api/src/model/Workshop.ts | 26 +- api/src/routes/index.ts | 8 +- api/src/routes/user.ts | 38 +-- api/src/routes/workshop.ts | 38 +-- api/src/server.ts | 26 +- api/tsconfig.json | 13 +- app/public/index.html | 2 +- app/src/App.test.tsx | 8 +- app/src/App.tsx | 16 +- app/src/api.ts | 62 ++--- app/src/components/AsyncSubmit.tsx | 10 +- app/src/components/Icon.tsx | 16 +- app/src/components/Modal.tsx | 22 +- app/src/components/Navbar.tsx | 22 +- app/src/index.css | 6 +- app/src/index.tsx | 20 +- app/src/pages/CreateGroup.tsx | 76 ++--- app/src/pages/CreateMeeting.tsx | 128 ++++----- app/src/pages/CreateWorkshop.tsx | 22 +- app/src/pages/Home.tsx | 130 ++++----- app/src/pages/MentorDashboard.tsx | 10 +- app/src/reportWebVitals.ts | 4 +- app/src/setupTests.ts | 2 +- app/src/styles/_components.scss | 211 +++++++------- app/src/styles/_formik.scss | 104 +++---- app/src/styles/_utilities.scss | 322 +++++++++++++++------- app/src/styles/_variables.scss | 91 +++--- app/src/styles/_widget.scss | 74 ++--- app/src/styles/main.scss | 92 +++---- app/tsconfig.json | 10 +- 34 files changed, 908 insertions(+), 791 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1286ad9..d8f4ac2 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,7 +13,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v2 with: - node-version: '16' + node-version: "16" - name: Install dependencies run: npm install @@ -21,4 +21,4 @@ jobs: - name: Run Prettier run: npm run format - working-directory: ./api \ No newline at end of file + working-directory: ./api diff --git a/README.md b/README.md index ad2b7a2..12c6e52 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -# PennsylvaniaWomenWork \ No newline at end of file +# PennsylvaniaWomenWork diff --git a/api/src/config/db.ts b/api/src/config/db.ts index 3e19c51..553460f 100644 --- a/api/src/config/db.ts +++ b/api/src/config/db.ts @@ -1,17 +1,17 @@ -import mongoose from "mongoose" -import dotenv from "dotenv" -import path from "path" +import mongoose from "mongoose"; +import dotenv from "dotenv"; +import path from "path"; -dotenv.config({ path: path.resolve(__dirname, "../../.env") }) +dotenv.config({ path: path.resolve(__dirname, "../../.env") }); // Connect to MongoDB const dbConnect = async () => { try { - await mongoose.connect(process.env.MONGO_URI as string, {}) - console.log("Connected to MongoDB") + await mongoose.connect(process.env.MONGO_URI as string, {}); + console.log("Connected to MongoDB"); } catch (error) { - console.error(error) + console.error(error); } -} +}; -export default dbConnect +export default dbConnect; diff --git a/api/src/controllers/workshopController.ts b/api/src/controllers/workshopController.ts index 248c831..8fd36c4 100644 --- a/api/src/controllers/workshopController.ts +++ b/api/src/controllers/workshopController.ts @@ -1,39 +1,39 @@ // NO POPULATE VERSION -import { Request, Response } from 'express'; -import { Workshop } from '../model/Workshop'; +import { Request, Response } from "express"; +import { Workshop } from "../model/Workshop"; export const createWorkshop = async (req: Request, res: Response) => { - try { - const { mentorId, menteeId, textContent } = req.body; - - const newWorkshop = new Workshop({ - mentor: mentorId, - mentee: menteeId, - textContent, - }); - - const savedWorkshop = await newWorkshop.save(); - res.status(201).json(savedWorkshop); - } catch (error) { - res.status(500).json({ message: 'Error creating workshop', error }); - } + try { + const { mentorId, menteeId, textContent } = req.body; + + const newWorkshop = new Workshop({ + mentor: mentorId, + mentee: menteeId, + textContent, + }); + + const savedWorkshop = await newWorkshop.save(); + res.status(201).json(savedWorkshop); + } catch (error) { + res.status(500).json({ message: "Error creating workshop", error }); + } }; export const getWorkshop = async (req: Request, res: Response) => { - try { - const { id } = req.params; - - const workshop = await Workshop.findById(id); - - if (!workshop) { - return res.status(404).json({ message: 'Workshop not found' }); - } - - res.status(200).json(workshop); - } catch (error) { - res.status(500).json({ message: 'Error retrieving workshop', error }); + try { + const { id } = req.params; + + const workshop = await Workshop.findById(id); + + if (!workshop) { + return res.status(404).json({ message: "Workshop not found" }); } + + res.status(200).json(workshop); + } catch (error) { + res.status(500).json({ message: "Error retrieving workshop", error }); + } }; // POPULATE VERSION (if details of mentor/mentee objects are needed on the frontend like name or picture) @@ -44,13 +44,13 @@ export const getWorkshop = async (req: Request, res: Response) => { // export const createWorkshop = async (req: Request, res: Response) => { // try { // const { mentorId, menteeId, textContent } = req.body; - + // const newWorkshop = new Workshop({ // mentor: mentorId, // mentee: menteeId, // textContent, // }); - + // const savedWorkshop = await newWorkshop.save(); // res.status(201).json(savedWorkshop); // } catch (error) { @@ -61,15 +61,15 @@ export const getWorkshop = async (req: Request, res: Response) => { // export const getWorkshop = async (req: Request, res: Response) => { // try { // const { id } = req.params; - + // const workshop = await Workshop.findById(id) // .populate('mentor') // Populate full user details for mentor // .populate('mentee'); // Populate full user details for mentee - + // if (!workshop) { // return res.status(404).json({ message: 'Workshop not found' }); // } - + // res.status(200).json(workshop); // } catch (error) { // res.status(500).json({ message: 'Error retrieving workshop', error }); diff --git a/api/src/model/Workshop.ts b/api/src/model/Workshop.ts index 54a188b..a425719 100644 --- a/api/src/model/Workshop.ts +++ b/api/src/model/Workshop.ts @@ -1,12 +1,12 @@ -import mongoose, { Schema, Document } from "mongoose" +import mongoose, { Schema, Document } from "mongoose"; // interface interface IWorkshop extends Document { - mentor: Schema.Types.ObjectId // TODO: add mentor type - mentee: Schema.Types.ObjectId // TODO: add mentee type - textContent: string - createdAt: Date + mentor: Schema.Types.ObjectId; // TODO: add mentor type + mentee: Schema.Types.ObjectId; // TODO: add mentee type + textContent: string; + createdAt: Date; - updateContent(newContent: string): Promise + updateContent(newContent: string): Promise; } // workshop schema @@ -15,16 +15,16 @@ const WorkshopSchema: Schema = new Schema({ mentee: { type: Schema.Types.ObjectId, ref: "User", required: true }, textContent: { type: String, required: true }, createdAt: { type: Date, default: Date.now }, -}) +}); // update text content of the workshop WorkshopSchema.methods.updateContent = async function ( - newContent: string + newContent: string, ): Promise { - this.textContent = newContent - await this.save() -} + this.textContent = newContent; + await this.save(); +}; // mongoose model -const Workshop = mongoose.model("Workshop", WorkshopSchema) -export { Workshop, IWorkshop } +const Workshop = mongoose.model("Workshop", WorkshopSchema); +export { Workshop, IWorkshop }; diff --git a/api/src/routes/index.ts b/api/src/routes/index.ts index e774f40..4bf9a11 100644 --- a/api/src/routes/index.ts +++ b/api/src/routes/index.ts @@ -1,9 +1,9 @@ -import user from "./user" -import express from "express" -import workshop from "./workshop" // Import workshop routes +import user from "./user"; +import express from "express"; +import workshop from "./workshop"; // Import workshop routes // export { workshops } -export { user, workshop } +export { user, workshop }; // import express, { Request, Response, Application } from "express" // import cors from "cors" diff --git a/api/src/routes/user.ts b/api/src/routes/user.ts index cbc829b..a734b81 100644 --- a/api/src/routes/user.ts +++ b/api/src/routes/user.ts @@ -1,11 +1,11 @@ -import express from "express" -import mongoose from "mongoose" -import dbConnect from "../config/db" +import express from "express"; +import mongoose from "mongoose"; +import dbConnect from "../config/db"; -const router = express.Router() +const router = express.Router(); // Call the dbConnect function to connect to MongoDB -dbConnect() +dbConnect(); // User schema definition const userSchema = new mongoose.Schema({ @@ -18,9 +18,9 @@ const userSchema = new mongoose.Schema({ menteeInfo: [String], // For mentors only meetingSchedule: [String], // For mentees only mentorData: String, // For mentees only -}) +}); -const User = mongoose.model("User", userSchema) +const User = mongoose.model("User", userSchema); // Route to create a new user router.post("/create-user", async (req: any, res: any) => { @@ -34,10 +34,10 @@ router.post("/create-user", async (req: any, res: any) => { menteeInfo, meetingSchedule, mentorData, - } = req.body + } = req.body; if (!firstName || !lastName || !username || !email || !role) { - return res.status(400).json({ message: "Missing required fields" }) + return res.status(400).json({ message: "Missing required fields" }); } // Create a new user based on role @@ -51,24 +51,24 @@ router.post("/create-user", async (req: any, res: any) => { menteeInfo: role === "mentor" ? menteeInfo : undefined, meetingSchedule: role === "mentee" ? meetingSchedule : undefined, mentorData: role === "mentee" ? mentorData : undefined, - }) + }); try { - const savedUser = await newUser.save() + const savedUser = await newUser.save(); res .status(201) - .json({ message: "User created successfully", user: savedUser }) + .json({ message: "User created successfully", user: savedUser }); } catch (error) { - res.status(400).json({ message: "Failed to create user", error }) + res.status(400).json({ message: "Failed to create user", error }); } -}) +}); // Test route to check if the API is working router.post("/test", async (req: any, res: any) => { - console.log("Received group data:") - const { name } = req.body + console.log("Received group data:"); + const { name } = req.body; - return res.status(200).json({ name }) -}) + return res.status(200).json({ name }); +}); -export default router +export default router; diff --git a/api/src/routes/workshop.ts b/api/src/routes/workshop.ts index 9d737c2..4fa779c 100644 --- a/api/src/routes/workshop.ts +++ b/api/src/routes/workshop.ts @@ -1,55 +1,55 @@ -import express from "express" -import mongoose from "mongoose" -import dbConnect from "../config/db" // Import the dbConnect function +import express from "express"; +import mongoose from "mongoose"; +import dbConnect from "../config/db"; // Import the dbConnect function -import { createWorkshop, getWorkshop } from "../controllers/workshopController" +import { createWorkshop, getWorkshop } from "../controllers/workshopController"; -const router = express.Router() +const router = express.Router(); // Call the dbConnect function to connect to MongoDB -dbConnect() +dbConnect(); // Workshop schema definition (name and S3 bucket ID) const workshopIDSchema = new mongoose.Schema({ name: String, s3ID: String, -}) +}); -const Workshop = mongoose.model("WorkshopID", workshopIDSchema) +const Workshop = mongoose.model("WorkshopID", workshopIDSchema); // Route to create a new workshop router.post("/create-workshop", async (req: any, res: any) => { - const { name, s3id } = req.body + const { name, s3id } = req.body; if (!name || !s3id) { - return res.status(400).json({ message: "Missing required fields" }) + return res.status(400).json({ message: "Missing required fields" }); } // Create a new workshop const newWorkshop = new Workshop({ name, s3id, - }) + }); try { - const savedWorkshop = await newWorkshop.save() + const savedWorkshop = await newWorkshop.save(); res.status(201).json({ message: "Workshop created successfully", WorkshopID: savedWorkshop, - }) + }); } catch (error) { - res.status(401).json({ message: "Failed to create workshop", error }) + res.status(401).json({ message: "Failed to create workshop", error }); } -}) +}); // router.post("/workshops", createWorkshop) // router.get('/workshops/:id', getWorkshop); router.get( "/workshops/:id", async (req: express.Request, res: express.Response) => { - await getWorkshop(req, res) - } -) + await getWorkshop(req, res); + }, +); // POPULATE VERSION (if details of mentor/mentee objects are needed on the frontend like name or picture) @@ -63,5 +63,5 @@ router.get( // export default router; -export default router +export default router; // NO POPULATE VERSION diff --git a/api/src/server.ts b/api/src/server.ts index a601f88..cd7a639 100644 --- a/api/src/server.ts +++ b/api/src/server.ts @@ -1,19 +1,19 @@ -import express from "express" -import bodyParser from "body-parser" -import connectDB from "./config/db" +import express from "express"; +import bodyParser from "body-parser"; +import connectDB from "./config/db"; -import * as routes from "./routes/index" +import * as routes from "./routes/index"; -var cors = require("cors") +var cors = require("cors"); -const app = express() -app.use(cors()) -app.use(bodyParser.json()) -app.use(bodyParser.urlencoded({ extended: true })) +const app = express(); +app.use(cors()); +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: true })); -app.use("/user", routes.user) -app.use("/workshopid", routes.workshopid) +app.use("/user", routes.user); +app.use("/workshop", routes.workshop); -connectDB() +connectDB(); -app.listen(process.env.PORT || 8000, () => console.log("Server running...")) +app.listen(process.env.PORT || 8000, () => console.log("Server running...")); diff --git a/api/tsconfig.json b/api/tsconfig.json index d8dc2d4..66debcc 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -13,7 +13,7 @@ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ /* Language and Environment */ - "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "jsx": "preserve", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ @@ -27,7 +27,7 @@ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ /* Modules */ - "module": "commonjs", /* Specify what module code is generated. */ + "module": "commonjs" /* Specify what module code is generated. */, // "rootDir": "./", /* Specify the root folder within your source files. */ // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ @@ -79,12 +79,12 @@ // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ + "strict": true /* Enable all strict type-checking options. */, // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ @@ -107,9 +107,8 @@ /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true, /* Skip type checking all .d.ts files. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */, "moduleResolution": "node", "typeRoots": ["./node_modules/@types"] } } - diff --git a/app/public/index.html b/app/public/index.html index aa069f2..e65acb3 100644 --- a/app/public/index.html +++ b/app/public/index.html @@ -1,4 +1,4 @@ - + diff --git a/app/src/App.test.tsx b/app/src/App.test.tsx index 2a68616..d76787e 100644 --- a/app/src/App.test.tsx +++ b/app/src/App.test.tsx @@ -1,8 +1,8 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import App from './App'; +import React from "react"; +import { render, screen } from "@testing-library/react"; +import App from "./App"; -test('renders learn react link', () => { +test("renders learn react link", () => { render(); const linkElement = screen.getByText(/learn react/i); expect(linkElement).toBeInTheDocument(); diff --git a/app/src/App.tsx b/app/src/App.tsx index 839c179..acc3ce2 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -1,9 +1,9 @@ -import React, { type ReactElement } from "react" -import { BrowserRouter as Router, Routes, Route } from "react-router-dom" -import Home from "./pages/Home" -import MentorDashboard from "./pages/MentorDashboard" -import CreateWorkshop from "./pages/CreateWorkshop" -import CreateMeeting from "./pages/CreateMeeting" +import React, { type ReactElement } from "react"; +import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; +import Home from "./pages/Home"; +import MentorDashboard from "./pages/MentorDashboard"; +import CreateWorkshop from "./pages/CreateWorkshop"; +import CreateMeeting from "./pages/CreateMeeting"; function App(): ReactElement { return (
@@ -17,7 +17,7 @@ function App(): ReactElement {
- ) + ); } -export default App +export default App; diff --git a/app/src/api.ts b/app/src/api.ts index c7ae4b3..b68ffce 100644 --- a/app/src/api.ts +++ b/app/src/api.ts @@ -1,6 +1,6 @@ export const api: any = { get: async (route: string): Promise => { - const url = `${process.env.REACT_APP_API_URL as string}${route}` + const url = `${process.env.REACT_APP_API_URL as string}${route}`; return await fetch(url, { method: "GET", @@ -8,24 +8,24 @@ export const api: any = { }) .then(async (res) => { if (!res.ok) { - throw new Error("Network response was not ok") + throw new Error("Network response was not ok"); } - const json = await res.json() + const json = await res.json(); const response = { data: json, status: res.status, - } + }; - return response + return response; }) .catch((err) => { - console.error("Error fetching data: ", err) - throw err - }) + console.error("Error fetching data: ", err); + throw err; + }); }, post: async (route: string, payload: any): Promise => { - const url = `${process.env.REACT_APP_API_URL as string}${route}` + const url = `${process.env.REACT_APP_API_URL as string}${route}`; return await fetch(url, { method: "POST", @@ -36,22 +36,22 @@ export const api: any = { }) .then(async (res) => { if (!res.ok) { - throw new Error("Network response was not ok") + throw new Error("Network response was not ok"); } - const json = await res.json() + const json = await res.json(); return { data: json, status: res.status, - } + }; }) .catch((err) => { - console.error("Error posting data: ", err) - throw err - }) + console.error("Error posting data: ", err); + throw err; + }); }, put: async (route: string, data: any): Promise => { - const url = `${process.env.REACT_APP_API_URL as string}${route}` + const url = `${process.env.REACT_APP_API_URL as string}${route}`; return await fetch(url, { method: "PUT", @@ -63,21 +63,21 @@ export const api: any = { body: data, }) .then(async (res) => { - const json = await res.json() + const json = await res.json(); const response = { data: json, status: res.status, - } - return response + }; + return response; }) .catch((err) => { - console.error("Error posting data: ", err) - throw err - }) + console.error("Error posting data: ", err); + throw err; + }); }, delete: async (route: string, data: any): Promise => { - const url = `${process.env.REACT_APP_API_URL as string}${route}` + const url = `${process.env.REACT_APP_API_URL as string}${route}`; return await fetch(url, { method: "DELETE", @@ -89,19 +89,19 @@ export const api: any = { }) .then(async (res) => { if (!res.ok) { - throw new Error("Network response was not ok") + throw new Error("Network response was not ok"); } - const json = await res.json() + const json = await res.json(); const response = { data: json, status: res.status, - } + }; - return response + return response; }) .catch((err) => { - console.error("Error deleting data: ", err) - throw err - }) + console.error("Error deleting data: ", err); + throw err; + }); }, -} +}; diff --git a/app/src/components/AsyncSubmit.tsx b/app/src/components/AsyncSubmit.tsx index c968f5e..7414bc7 100644 --- a/app/src/components/AsyncSubmit.tsx +++ b/app/src/components/AsyncSubmit.tsx @@ -1,5 +1,5 @@ -import React, { type ReactElement } from "react" -import { Spinner } from "react-bootstrap" +import React, { type ReactElement } from "react"; +import { Spinner } from "react-bootstrap"; const AsyncSubmit = (props: { loading: boolean }): ReactElement => { return ( @@ -10,7 +10,7 @@ const AsyncSubmit = (props: { loading: boolean }): ReactElement => { )} - ) -} + ); +}; -export default AsyncSubmit +export default AsyncSubmit; diff --git a/app/src/components/Icon.tsx b/app/src/components/Icon.tsx index 4c1c3c8..6d615ec 100644 --- a/app/src/components/Icon.tsx +++ b/app/src/components/Icon.tsx @@ -3,22 +3,22 @@ */ // eslint-disable-next-line @typescript-eslint/consistent-type-imports -import React, { FC } from "react" +import React, { FC } from "react"; interface IconProps { - glyph: string - regular?: boolean - size?: string + glyph: string; + regular?: boolean; + size?: string; } const Icon: FC = ({ glyph, regular = false, size = "" }) => { - const iconClass = `fa${regular ? "r" : "s"} fa-${glyph}` + const iconClass = `fa${regular ? "r" : "s"} fa-${glyph}`; return ( - ) -} + ); +}; -export default Icon +export default Icon; diff --git a/app/src/components/Modal.tsx b/app/src/components/Modal.tsx index 722bcf4..dfd9935 100644 --- a/app/src/components/Modal.tsx +++ b/app/src/components/Modal.tsx @@ -2,15 +2,15 @@ This component is a modal that can be used to display information to the user. */ -import React, { type ReactElement } from "react" -import Icon from "./Icon" +import React, { type ReactElement } from "react"; +import Icon from "./Icon"; interface ModalProps { - header?: string - subheader?: string - body?: ReactElement - large?: boolean - action?: () => void + header?: string; + subheader?: string; + body?: ReactElement; + large?: boolean; + action?: () => void; } const Modal = (props: ModalProps): ReactElement => { @@ -19,7 +19,7 @@ const Modal = (props: ModalProps): ReactElement => {
@@ -36,7 +36,7 @@ const Modal = (props: ModalProps): ReactElement => {
{props.body}
- ) -} + ); +}; -export default Modal +export default Modal; diff --git a/app/src/components/Navbar.tsx b/app/src/components/Navbar.tsx index cc91215..f0f8ef6 100644 --- a/app/src/components/Navbar.tsx +++ b/app/src/components/Navbar.tsx @@ -1,15 +1,15 @@ -import react, { type ReactElement } from "react" -import { useNavigate } from "react-router-dom" +import react, { type ReactElement } from "react"; +import { useNavigate } from "react-router-dom"; const Navbar = (): ReactElement => { - const navigate = useNavigate() + const navigate = useNavigate(); return ( <>
{ - navigate("/home") + navigate("/home"); }} className="Navbar-body-logo" >
@@ -17,7 +17,7 @@ const Navbar = (): ReactElement => {
{ - navigate("/home") + navigate("/home"); }} > Home @@ -25,7 +25,7 @@ const Navbar = (): ReactElement => {
{ - navigate("/mentor") + navigate("/mentor"); }} > Mentor @@ -33,7 +33,7 @@ const Navbar = (): ReactElement => {
{ - navigate("/create-workshop") + navigate("/create-workshop"); }} > Create Workshop @@ -41,7 +41,7 @@ const Navbar = (): ReactElement => {
{ - navigate("/create-meeting") + navigate("/create-meeting"); }} > Create Meeting @@ -50,7 +50,7 @@ const Navbar = (): ReactElement => {
- ) -} + ); +}; -export default Navbar +export default Navbar; diff --git a/app/src/index.css b/app/src/index.css index ec2585e..4a1df4d 100644 --- a/app/src/index.css +++ b/app/src/index.css @@ -1,13 +1,13 @@ body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } diff --git a/app/src/index.tsx b/app/src/index.tsx index 1e0f6f6..cf4e546 100644 --- a/app/src/index.tsx +++ b/app/src/index.tsx @@ -1,17 +1,19 @@ -import React from "react" -import ReactDOM from "react-dom/client" -import "./styles/main.scss" -import App from "./App" -import reportWebVitals from "./reportWebVitals" +import React from "react"; +import ReactDOM from "react-dom/client"; +import "./styles/main.scss"; +import App from "./App"; +import reportWebVitals from "./reportWebVitals"; -const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement) +const root = ReactDOM.createRoot( + document.getElementById("root") as HTMLElement, +); root.render( - -) + , +); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals() +reportWebVitals(); diff --git a/app/src/pages/CreateGroup.tsx b/app/src/pages/CreateGroup.tsx index 03ac124..ff9fa0d 100644 --- a/app/src/pages/CreateGroup.tsx +++ b/app/src/pages/CreateGroup.tsx @@ -1,28 +1,28 @@ -import React, { type ReactElement, useState } from "react" -import Navbar from "../components/Navbar" -import AsyncSubmit from "../components/AsyncSubmit" -import { api } from "../api" -import { useNavigate } from "react-router-dom" -import * as yup from "yup" -import { Formik, Form, Field } from "formik" +import React, { type ReactElement, useState } from "react"; +import Navbar from "../components/Navbar"; +import AsyncSubmit from "../components/AsyncSubmit"; +import { api } from "../api"; +import { useNavigate } from "react-router-dom"; +import * as yup from "yup"; +import { Formik, Form, Field } from "formik"; interface GroupFormValues { - groupName: string - groupDescription: string - groupMemberEmail: string + groupName: string; + groupDescription: string; + groupMemberEmail: string; } const initialValues: GroupFormValues = { groupName: "", groupDescription: "", groupMemberEmail: "", -} +}; const CreateGroup = (): ReactElement => { - const [isLoading, setIsLoading] = useState(false) - const [success, setSuccess] = useState(false) - const [members, setMembers] = useState([]) - const navigate = useNavigate() + const [isLoading, setIsLoading] = useState(false); + const [success, setSuccess] = useState(false); + const [members, setMembers] = useState([]); + const navigate = useNavigate(); const validationSchema = yup.object().shape({ groupName: yup.string().required("Please enter a group name"), @@ -35,47 +35,47 @@ const CreateGroup = (): ReactElement => { "Please enter an email or add a group member", function (value) { // Validate only if there's no value and no members added - return value !== "" || members.length > 0 - } + return value !== "" || members.length > 0; + }, ), - }) + }); const handleSubmit = async (values: GroupFormValues, { resetForm }: any) => { - setIsLoading(true) + setIsLoading(true); // Construct the group data to send to the backend const groupData = { groupName: values.groupName, groupDescription: values.groupDescription, members: members, // Use the members state array - } + }; - console.log("Sending group data:", groupData) // Optional: Log the data being sent + console.log("Sending group data:", groupData); // Optional: Log the data being sent try { // Send POST request to the /create-group endpoint - const response = await api.post("/create-group/:", groupData) + const response = await api.post("/create-group/:", groupData); - console.log("Group created successfully:", response.data) - setSuccess(true) // Set success state to true - resetForm() // Reset the form after successful submission - setMembers([]) // Clear the members array as well + console.log("Group created successfully:", response.data); + setSuccess(true); // Set success state to true + resetForm(); // Reset the form after successful submission + setMembers([]); // Clear the members array as well } catch (error) { console.error( "Error creating group:", - (error as any).response?.data || (error as any).message - ) + (error as any).response?.data || (error as any).message, + ); } finally { - setIsLoading(false) // Reset loading state + setIsLoading(false); // Reset loading state } - } + }; const addMember = (email: string, resetField: () => void) => { if (email && !members.includes(email)) { - setMembers([...members, email]) - resetField() // Clear the email input field + setMembers([...members, email]); + resetField(); // Clear the email input field } - } + }; return ( <> @@ -148,8 +148,8 @@ const CreateGroup = (): ReactElement => { className="Button Button-color--purple-1000 Margin-left--10" onClick={() => { addMember(values.groupMemberEmail, () => - setFieldValue("groupMemberEmail", "") - ) + setFieldValue("groupMemberEmail", ""), + ); }} > Add @@ -195,7 +195,7 @@ const CreateGroup = (): ReactElement => {
- ) -} + ); +}; -export default CreateGroup +export default CreateGroup; diff --git a/app/src/pages/CreateMeeting.tsx b/app/src/pages/CreateMeeting.tsx index 0854af2..5cdf493 100644 --- a/app/src/pages/CreateMeeting.tsx +++ b/app/src/pages/CreateMeeting.tsx @@ -1,66 +1,66 @@ -import React, { useState } from "react" -import { Formik, Form, Field, FieldArray } from "formik" -import Navbar from "../components/Navbar" -import * as Yup from "yup" +import React, { useState } from "react"; +import { Formik, Form, Field, FieldArray } from "formik"; +import Navbar from "../components/Navbar"; +import * as Yup from "yup"; const CreateMeeting = () => { - const initialValues = { - meeting: "", - notes: "", - } - const validationSchema = Yup.object().shape({ - meeting: Yup.string().required("Please enter a meeting name"), - notes: Yup.string().required("Please enter meeting notes"), - }) - const handleSubmit = (values: any) => { - console.log(values) - } - return ( - <> - -

Create Meeting

- - {({ values, errors, touched, isSubmitting }) => ( -
-
- - - {errors.meeting && touched.meeting && ( -
{errors.meeting}
- )} -
-
- - - {errors.notes && touched.notes && ( -
{errors.notes}
- )} -
- -
- )} -
- - ) - } - - export default CreateMeeting \ No newline at end of file + const initialValues = { + meeting: "", + notes: "", + }; + const validationSchema = Yup.object().shape({ + meeting: Yup.string().required("Please enter a meeting name"), + notes: Yup.string().required("Please enter meeting notes"), + }); + const handleSubmit = (values: any) => { + console.log(values); + }; + return ( + <> + +

Create Meeting

+ + {({ values, errors, touched, isSubmitting }) => ( +
+
+ + + {errors.meeting && touched.meeting && ( +
{errors.meeting}
+ )} +
+
+ + + {errors.notes && touched.notes && ( +
{errors.notes}
+ )} +
+ +
+ )} +
+ + ); +}; + +export default CreateMeeting; diff --git a/app/src/pages/CreateWorkshop.tsx b/app/src/pages/CreateWorkshop.tsx index 3e02e76..fa1e61d 100644 --- a/app/src/pages/CreateWorkshop.tsx +++ b/app/src/pages/CreateWorkshop.tsx @@ -1,25 +1,25 @@ -import React from "react" -import { Formik, Form, Field } from "formik" -import * as Yup from "yup" -import Navbar from "../components/Navbar" +import React from "react"; +import { Formik, Form, Field } from "formik"; +import * as Yup from "yup"; +import Navbar from "../components/Navbar"; const CreateWorkshop = () => { // Initial form values const initialValues = { name: "", description: "", - } + }; // Validation schema using Yup const validationSchema = Yup.object().shape({ name: Yup.string().required("Name is required"), description: Yup.string().required("Description is required"), - }) + }); // Handle form submission const handleSubmit = (values: any) => { - console.log(values) - } + console.log(values); + }; return ( <> @@ -71,7 +71,7 @@ const CreateWorkshop = () => { )} - ) -} + ); +}; -export default CreateWorkshop +export default CreateWorkshop; diff --git a/app/src/pages/Home.tsx b/app/src/pages/Home.tsx index 41d9e5d..b57d475 100644 --- a/app/src/pages/Home.tsx +++ b/app/src/pages/Home.tsx @@ -1,18 +1,18 @@ -import React, { useState } from "react" -import { Formik, Form, Field, FieldArray } from "formik" -import * as yup from "yup" -import Navbar from "../components/Navbar" -import AsyncSubmit from "../components/AsyncSubmit" -import { useNavigate } from "react-router-dom" -import Modal from "../components/Modal" +import React, { useState } from "react"; +import { Formik, Form, Field, FieldArray } from "formik"; +import * as yup from "yup"; +import Navbar from "../components/Navbar"; +import AsyncSubmit from "../components/AsyncSubmit"; +import { useNavigate } from "react-router-dom"; +import Modal from "../components/Modal"; const Home = () => { - const [isModal, setIsModal] = useState(false) - const navigate = useNavigate() - const [isLoading, setIsLoading] = useState(false) - const [success, setSuccess] = useState(false) - const [selection, setSelection] = useState("Equally") // Default to "Equally" - const [errorMessage, setErrorMessage] = useState("") + const [isModal, setIsModal] = useState(false); + const navigate = useNavigate(); + const [isLoading, setIsLoading] = useState(false); + const [success, setSuccess] = useState(false); + const [selection, setSelection] = useState("Equally"); // Default to "Equally" + const [errorMessage, setErrorMessage] = useState(""); const initialValues = { item: "", @@ -23,7 +23,7 @@ const Home = () => { { name: "Jane", selected: false, splitValue: undefined }, { name: "Doe", selected: false, splitValue: undefined }, ], - } + }; const validationSchema = yup.object().shape({ item: yup.string().required("Please enter an item or activity"), @@ -32,50 +32,50 @@ const Home = () => { .positive("Cost must be a positive number") .required("Please enter the cost"), date: yup.string().required("Please select a date"), - }) + }); const handleSubmit = async (values: any, { resetForm }: any) => { - setIsLoading(true) - setErrorMessage("") // Clear error message at the start of submission + setIsLoading(true); + setErrorMessage(""); // Clear error message at the start of submission try { // Initialize a variable to hold the breakdown for each member - let memberBreakdown: { name: string; amountDue: any }[] = [] + let memberBreakdown: { name: string; amountDue: any }[] = []; // Calculate total amount owed based on the selection method - const totalCost = values.cost + const totalCost = values.cost; const selectedMembers = values.members.filter( - (member: { selected: any }) => member.selected - ) + (member: { selected: any }) => member.selected, + ); if (selection === "Equally") { // Split the total cost equally between the selected members - const amountPerMember = totalCost / selectedMembers.length + const amountPerMember = totalCost / selectedMembers.length; selectedMembers.forEach((member: { name: any }) => { memberBreakdown.push({ name: member.name, amountDue: amountPerMember, - }) - }) + }); + }); } else if (selection === "By Percent") { // Split the total cost based on the percentage provided for each selected member selectedMembers.forEach((member: { splitValue: number; name: any }) => { - const percentage = member.splitValue || 0 // default to 0 if no percentage provided - const amountDue = (percentage / 100) * totalCost + const percentage = member.splitValue || 0; // default to 0 if no percentage provided + const amountDue = (percentage / 100) * totalCost; memberBreakdown.push({ name: member.name, amountDue: amountDue, - }) - }) + }); + }); } else if (selection === "Manual") { // Use the manual amounts provided for each selected member selectedMembers.forEach((member: { splitValue: number; name: any }) => { - const amountDue = member.splitValue || 0 // default to 0 if no amount provided + const amountDue = member.splitValue || 0; // default to 0 if no amount provided memberBreakdown.push({ name: member.name, amountDue: amountDue, - }) - }) + }); + }); } // Package the final data to submit @@ -84,60 +84,62 @@ const Home = () => { totalCost: totalCost, date: values.date, memberBreakdown: memberBreakdown, - } + }; - console.log("Submitting data:", finalData) - setSuccess(true) - setErrorMessage("") // Ensure error message is cleared on success - resetForm() + console.log("Submitting data:", finalData); + setSuccess(true); + setErrorMessage(""); // Ensure error message is cleared on success + resetForm(); } catch (error) { - console.error("Error submitting:", error) + console.error("Error submitting:", error); } finally { - setIsLoading(false) + setIsLoading(false); } - } + }; // Custom validation logic to check split values const validateForm = (values: { members: any[]; cost: number }) => { - let errors = {} - let totalSplitValue = 0 + let errors = {}; + let totalSplitValue = 0; // Custom validation based on the selection if (selection !== "Equally") { - let membersSelected = values.members.filter((member) => member.selected) + let membersSelected = values.members.filter((member) => member.selected); if (membersSelected.length === 0) { - setErrorMessage("Please select at least one member.") - return errors + setErrorMessage("Please select at least one member."); + return errors; } membersSelected.forEach((member) => { if (!member.splitValue) { setErrorMessage( - "Please provide a split value for all selected members." - ) - return errors + "Please provide a split value for all selected members.", + ); + return errors; } totalSplitValue += - selection === "By Percent" ? member.splitValue : member.splitValue - }) + selection === "By Percent" ? member.splitValue : member.splitValue; + }); // Check if the split values sum up correctly if (selection === "By Percent" && totalSplitValue !== 100) { - setErrorMessage("The total percentage split must add up to 100%.") - return errors + setErrorMessage("The total percentage split must add up to 100%."); + return errors; } if (selection === "Manual" && totalSplitValue !== values.cost) { - setErrorMessage("The total split values must add up to the total cost.") - return errors + setErrorMessage( + "The total split values must add up to the total cost.", + ); + return errors; } } // If no error, clear the error message - setErrorMessage("") - return errors - } + setErrorMessage(""); + return errors; + }; return ( <> @@ -206,7 +208,7 @@ const Home = () => { : "Button Button-color--green-1000" } onClick={() => { - setSelection("Equally") + setSelection("Equally"); }} > Equally @@ -218,7 +220,7 @@ const Home = () => { : "Button Button-color--green-1000" } onClick={() => { - setSelection("By Percent") + setSelection("By Percent"); }} > By Percent @@ -230,7 +232,7 @@ const Home = () => { : "Button Button-color--green-1000" } onClick={() => { - setSelection("Manual") + setSelection("Manual"); }} > Manual @@ -312,7 +314,7 @@ const Home = () => {

Home

{ - navigate("/create-group") + navigate("/create-group"); }} className="Button Button-color--dark-1000" > @@ -320,14 +322,14 @@ const Home = () => {
{ - setIsModal(true) + setIsModal(true); }} className="Button Button-color--dark-1000 Margin-top--10" > Open Modal
- ) -} + ); +}; -export default Home +export default Home; diff --git a/app/src/pages/MentorDashboard.tsx b/app/src/pages/MentorDashboard.tsx index 8abe82f..6f2b98c 100644 --- a/app/src/pages/MentorDashboard.tsx +++ b/app/src/pages/MentorDashboard.tsx @@ -1,5 +1,5 @@ -import React, { useState } from "react" -import Navbar from "../components/Navbar" +import React, { useState } from "react"; +import Navbar from "../components/Navbar"; const MentorDashboard = () => { return ( @@ -10,7 +10,7 @@ const MentorDashboard = () => {
Example Button
- ) -} + ); +}; -export default MentorDashboard +export default MentorDashboard; diff --git a/app/src/reportWebVitals.ts b/app/src/reportWebVitals.ts index 49a2a16..5fa3583 100644 --- a/app/src/reportWebVitals.ts +++ b/app/src/reportWebVitals.ts @@ -1,8 +1,8 @@ -import { ReportHandler } from 'web-vitals'; +import { ReportHandler } from "web-vitals"; const reportWebVitals = (onPerfEntry?: ReportHandler) => { if (onPerfEntry && onPerfEntry instanceof Function) { - import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { getCLS(onPerfEntry); getFID(onPerfEntry); getFCP(onPerfEntry); diff --git a/app/src/setupTests.ts b/app/src/setupTests.ts index 8f2609b..1dd407a 100644 --- a/app/src/setupTests.ts +++ b/app/src/setupTests.ts @@ -2,4 +2,4 @@ // allows you to do things like: // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom'; +import "@testing-library/jest-dom"; diff --git a/app/src/styles/_components.scss b/app/src/styles/_components.scss index 89959b6..f79bd2b 100644 --- a/app/src/styles/_components.scss +++ b/app/src/styles/_components.scss @@ -1,124 +1,123 @@ .Navbar { + display: flex; + flex-direction: column; + z-index: var(--pww-z-50); + + &-body { display: flex; - flex-direction: column; - z-index: var(--pww-z-50); - - &-body { - display: flex; - flex-direction: row; - align-items: center; - background-color: var(--pww-color-light-1000); - padding: 10px 32px; - box-shadow: var(--pww-shadow-dark); - - &-logo { - font-size: var(--pww-font-size-30); - font-weight: 900; - background-image: url('../assets/logo.png'); - background-size: contain; /* Ensure the entire logo fits within the defined area */ - background-repeat: no-repeat; /* Prevent repeating */ - background-position: center; /* Center the logo within its container */ - width: 125px; - height: 75px; /* Adjust height based on the image’s aspect ratio */ - } - - &-link { - color: var(--pww-color-dark-1000); - cursor: pointer; - font-size: var(--pww-font-size-16); - - &:hover { - color: var(--pww-color-teal-1000); - } - } + flex-direction: row; + align-items: center; + background-color: var(--pww-color-light-1000); + padding: 10px 32px; + box-shadow: var(--pww-shadow-dark); + + &-logo { + font-size: var(--pww-font-size-30); + font-weight: 900; + background-image: url("../assets/logo.png"); + background-size: contain; /* Ensure the entire logo fits within the defined area */ + background-repeat: no-repeat; /* Prevent repeating */ + background-position: center; /* Center the logo within its container */ + width: 125px; + height: 75px; /* Adjust height based on the image’s aspect ratio */ + } + + &-link { + color: var(--pww-color-dark-1000); + cursor: pointer; + font-size: var(--pww-font-size-16); + + &:hover { + color: var(--pww-color-teal-1000); + } } + } } .Modal-overlay { - background-color: rgba(0, 0, 0, .4); - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: var(--pww-z-100); - overflow: hidden; + background-color: rgba(0, 0, 0, 0.4); + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: var(--pww-z-100); + overflow: hidden; } .Modal { - position: fixed; - padding: 20px; - width: 400px; - z-index: var(--pww-z-1000); - top: 10%; - left: 50%; - margin-left: -200px; - background-color: var(--pww-color-light-1000); - border-radius: var(--pww-border-radius-8); - border: 1px solid rgba(var(--dark), var(--opacity-tint)); + position: fixed; + padding: 20px; + width: 400px; + z-index: var(--pww-z-1000); + top: 10%; + left: 50%; + margin-left: -200px; + background-color: var(--pww-color-light-1000); + border-radius: var(--pww-border-radius-8); + border: 1px solid rgba(var(--dark), var(--opacity-tint)); - &-large { - width: 700px; - margin-left: -350px; - max-height: 700px; - } + &-large { + width: 700px; + margin-left: -350px; + max-height: 700px; + } - &-close { - display: flex; - justify-content: right; - - .Icon { - font-size: var(--pww-font-size-16); - margin-left: auto; - - &:hover { - cursor: pointer; - color: var(--pww-color-red-1000); - } - } - } + &-close { + display: flex; + justify-content: right; - &-header { - font-size: var(--pww-font-size-24); - margin-bottom: 10px; - } + .Icon { + font-size: var(--pww-font-size-16); + margin-left: auto; - &-subtitle { - color: var(--pww-color-dark-700); - font-size: var(--pww-font-size-16); - border-bottom: 1px solid var(--pww-color-dark-700); - padding-bottom: 10px; - margin-bottom: 20px; + &:hover { + cursor: pointer; + color: var(--pww-color-red-1000); } - - &-content { - border-radius: var(--pww-border-radius-16); - color: var(--pww-color-dark-800); - z-index: 5; - height: fit-content; - max-height: 600px; - overflow-y: auto; - padding: 10px; } + } - &-tab { - cursor: pointer; - display: flex; - - &-item { - width: 70px; - padding-bottom: 5px; - text-align: center; - border-bottom: 1px solid var(--pww-color-light-600); - &-selected { - color: var(--pww-color-light-1000); - border-bottom: 3px solid var(--pww-color-light-1000); - } - - &:hover { - color: var(--pww-color-light-1000); - } - } + &-header { + font-size: var(--pww-font-size-24); + margin-bottom: 10px; + } + + &-subtitle { + color: var(--pww-color-dark-700); + font-size: var(--pww-font-size-16); + border-bottom: 1px solid var(--pww-color-dark-700); + padding-bottom: 10px; + margin-bottom: 20px; + } + + &-content { + border-radius: var(--pww-border-radius-16); + color: var(--pww-color-dark-800); + z-index: 5; + height: fit-content; + max-height: 600px; + overflow-y: auto; + padding: 10px; + } + + &-tab { + cursor: pointer; + display: flex; + + &-item { + width: 70px; + padding-bottom: 5px; + text-align: center; + border-bottom: 1px solid var(--pww-color-light-600); + &-selected { + color: var(--pww-color-light-1000); + border-bottom: 3px solid var(--pww-color-light-1000); + } + + &:hover { + color: var(--pww-color-light-1000); + } } + } } - diff --git a/app/src/styles/_formik.scss b/app/src/styles/_formik.scss index 0c58f64..40134bb 100644 --- a/app/src/styles/_formik.scss +++ b/app/src/styles/_formik.scss @@ -1,60 +1,60 @@ .Form { - color: var(--pww-color-dark-900); - - &-container { - width: 100%; - max-width: 600px; // Adjust max-width based on your layout - padding: 20px; - margin: 0 auto; // Center the form container + color: var(--pww-color-dark-900); + + &-container { + width: 100%; + max-width: 600px; // Adjust max-width based on your layout + padding: 20px; + margin: 0 auto; // Center the form container + } + + &-group { + display: flex; + flex-direction: column; + margin-bottom: 10px; + + label { + margin-bottom: 4px; + font-size: var(--pww-font-size-14); + color: var(--pww-color-dark-1000); + font-weight: var(--pww-font-weight-bold); } - &-group { - display: flex; - flex-direction: column; - margin-bottom: 10px; - - label { - margin-bottom: 4px; - font-size: var(--pww-font-size-14); - color: var(--pww-color-dark-1000); - font-weight: var(--pww-font-weight-bold); - } - - input, - select { - border-radius: var(--pww-border-radius-8); - border: none; - background-color: var(--pww-color-dark-50); - color: var(--pww-color-dark-700); - font-family: 'Exo 2', sans-serif; - font-size: var(--pww-font-size-14); - padding: 8px; - // width: 100%; // Ensure inputs fit within the container - box-sizing: border-box; // Include padding and border in width calculation - position: relative; - } - - &-toggle { - cursor: pointer; - margin: -30px 10px 0 auto; - z-index: var(--pww-z-100); - color: var(--pww-color-dark-700); - - &:hover { - color: var(--pww-color-dark-900); - } - } + input, + select { + border-radius: var(--pww-border-radius-8); + border: none; + background-color: var(--pww-color-dark-50); + color: var(--pww-color-dark-700); + font-family: "Exo 2", sans-serif; + font-size: var(--pww-font-size-14); + padding: 8px; + // width: 100%; // Ensure inputs fit within the container + box-sizing: border-box; // Include padding and border in width calculation + position: relative; } - &-error { - margin-top: 10px; - color: var(--pww-color-red-1000); - font-size: var(--pww-font-size-12); - } + &-toggle { + cursor: pointer; + margin: -30px 10px 0 auto; + z-index: var(--pww-z-100); + color: var(--pww-color-dark-700); - &-success { - margin-top: 10px; - color: var(--pww-color-green-1000); - font-size: var(--pww-font-size-12); + &:hover { + color: var(--pww-color-dark-900); + } } + } + + &-error { + margin-top: 10px; + color: var(--pww-color-red-1000); + font-size: var(--pww-font-size-12); + } + + &-success { + margin-top: 10px; + color: var(--pww-color-green-1000); + font-size: var(--pww-font-size-12); + } } diff --git a/app/src/styles/_utilities.scss b/app/src/styles/_utilities.scss index 3d202e1..0f590c0 100644 --- a/app/src/styles/_utilities.scss +++ b/app/src/styles/_utilities.scss @@ -1,7 +1,11 @@ @each $name, $color in $palette { @each $alpha in $alphas { - .Text-color--#{$name}-#{$alpha} { color: var(--pww-color-#{$name}-#{$alpha}); } - .Background-color--#{$name}-#{$alpha} { background-color: var(--pww-color-#{$name}-#{$alpha}); } + .Text-color--#{$name}-#{$alpha} { + color: var(--pww-color-#{$name}-#{$alpha}); + } + .Background-color--#{$name}-#{$alpha} { + background-color: var(--pww-color-#{$name}-#{$alpha}); + } .Background-colorHover--#{$name}-#{$alpha} { cursor: pointer; @@ -13,7 +17,6 @@ } } - .Text-colorHover--#{$name}-#{$alpha} { cursor: pointer; @@ -22,7 +25,9 @@ } } - .Border-color--#{$name}-#{$alpha} { border-color: var(--pww-color-#{$name}-#{$alpha}); } + .Border-color--#{$name}-#{$alpha} { + border-color: var(--pww-color-#{$name}-#{$alpha}); + } } } @@ -30,9 +35,9 @@ .Text-fontSize--#{$fontSize} { font-size: #{$fontSize}px; - // @include media("<=tablet") { - // font-size: #{$fontSize - 2}px; - // } + // @include media("<=tablet") { + // font-size: #{$fontSize - 2}px; + // } } // .Text-fontSize--#{$fontSize}--ltDesktop { @@ -130,13 +135,28 @@ } @each $margin in $margins { - .Margin--#{$margin} { margin: #{$margin}px; } - .Margin-left--#{$margin} { margin-left: #{$margin}px; } - .Margin-top--#{$margin} { margin-top: #{$margin}px; } - .Margin-right--#{$margin} { margin-right: #{$margin}px; } - .Margin-bottom--#{$margin} { margin-bottom: #{$margin}px; } - .Margin-x--#{$margin} { margin: 0 #{$margin}px; } - .Margin-y--#{$margin} { margin-top: #{$margin}px; margin-bottom: #{$margin}px; } + .Margin--#{$margin} { + margin: #{$margin}px; + } + .Margin-left--#{$margin} { + margin-left: #{$margin}px; + } + .Margin-top--#{$margin} { + margin-top: #{$margin}px; + } + .Margin-right--#{$margin} { + margin-right: #{$margin}px; + } + .Margin-bottom--#{$margin} { + margin-bottom: #{$margin}px; + } + .Margin-x--#{$margin} { + margin: 0 #{$margin}px; + } + .Margin-y--#{$margin} { + margin-top: #{$margin}px; + margin-bottom: #{$margin}px; + } // @include media(" 50, var(--pww-color-dark-1000), var(--pww-color-light-1000)); + @return if( + lightness($color) > 50, + var(--pww-color-dark-1000), + var(--pww-color-light-1000) + ); // @return if(true, var(--pww-color-dark-1000), var(--pww-color-light-1000)); } @@ -71,7 +76,7 @@ $zIndex: -10 -5 0 5 10 50 100 1000; --pww-z-#{$zIndex}: #{$zIndex}; } - --pww-shadow-dark: var(--pww-color-dark-50) 0px 7px 15px 0px; + --pww-shadow-dark: var(--pww-color-dark-50) 0px 7px 15px 0px; --pww-shadow-ambient: var(--pww-color-dark-100) 0px 15px 35px 0px; } @@ -80,30 +85,34 @@ $weights: 25 50 100 200 300 400 500 600 700 800 900 !default; // defines more variables for the app @mixin root-palette($palette) { - @each $name, $color in $palette { - --#{$name}: #{red($color)}, #{green($color)}, #{blue($color)}; - --#{$name}-dark: #{red(saturate($color, 20))}, #{green(saturate($color, 20))}, #{blue(saturate($color, 20))}; - --#{$name}-light: #{red(lighten($color, 20))}, #{green(lighten($color, 20))}, #{blue(lighten($color, 20))}; - } + @each $name, $color in $palette { + --#{$name}: #{red($color)}, #{green($color)}, #{blue($color)}; + --#{$name}-dark: #{red(saturate($color, 20))}, + #{green(saturate($color, 20))}, #{blue(saturate($color, 20))}; + --#{$name}-light: #{red(lighten($color, 20))}, #{green(lighten($color, 20))}, + #{blue(lighten($color, 20))}; } - - @mixin root-property($property, $base, $factors) { - @for $i from 1 through length($factors) { - --#{$property}-#{$i - 1}: #{$base * nth($factors, $i)}; - } +} + +@mixin root-property($property, $base, $factors) { + @for $i from 1 through length($factors) { + --#{$property}-#{$i - 1}: #{$base * nth($factors, $i)}; } - - @mixin root-color($name, $color) { - @each $weight in $weights { - --color-#{$name}-#{$weight}: #{lighten($color, math.div((1000 - $weight), 10))}; - } +} + +@mixin root-color($name, $color) { + @each $weight in $weights { + --color-#{$name}-#{$weight}: #{lighten( + $color, + math.div((1000 - $weight), 10) + )}; } - - @mixin utility($palette, $prefix, $attribute) { - @each $name, $color in $palette { - .#{$prefix}-#{$name} { - #{$attribute}: #{$color}; - } +} + +@mixin utility($palette, $prefix, $attribute) { + @each $name, $color in $palette { + .#{$prefix}-#{$name} { + #{$attribute}: #{$color}; } } - +} diff --git a/app/src/styles/_widget.scss b/app/src/styles/_widget.scss index 96fdd20..3df1273 100644 --- a/app/src/styles/_widget.scss +++ b/app/src/styles/_widget.scss @@ -1,47 +1,47 @@ .FormWidget { - display: flex; - justify-content: center; - margin-top: 5%; + display: flex; + justify-content: center; + margin-top: 5%; - &-body { - width: 30%; - height: fit-content; + &-body { + width: 30%; + height: fit-content; - // &-logo { - // display: block; - // background-image: url('../assets/pww-Logo-White.png'); - // background-size: contain; - // background-repeat: no-repeat; - // height: 60px; - // width: auto; - // margin: 56px 0 40px 20px; - // } + // &-logo { + // display: block; + // background-image: url('../assets/pww-Logo-White.png'); + // background-size: contain; + // background-repeat: no-repeat; + // height: 60px; + // width: auto; + // margin: 56px 0 40px 20px; + // } - &-content { - padding: 56px 48px; - box-shadow: var(--pww-shadow-ambient); - background-color: var(--pww-color-dark-1000); - border-radius: var(--pww-border-radius-8); - } + &-content { + padding: 56px 48px; + box-shadow: var(--pww-shadow-ambient); + background-color: var(--pww-color-dark-1000); + border-radius: var(--pww-border-radius-8); } + } } .SuccessWidget { - display: flex; - justify-content: center; - align-items: center; - height: 100vh; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; - &-body { - width: 30%; - height: fit-content; - } + &-body { + width: 30%; + height: fit-content; + } - &-icon { - width: 100; - display: flex; - justify-content: center; - color: var(--pww-color-yellow-1000); - font-size: var(--pww-font-size-72); - } -} \ No newline at end of file + &-icon { + width: 100; + display: flex; + justify-content: center; + color: var(--pww-color-yellow-1000); + font-size: var(--pww-font-size-72); + } +} diff --git a/app/src/styles/main.scss b/app/src/styles/main.scss index a1bc20b..3de83af 100644 --- a/app/src/styles/main.scss +++ b/app/src/styles/main.scss @@ -1,53 +1,53 @@ // Imports for all of the external libraries -@import url('https://fonts.googleapis.com/css?family=Rubik'); -@import url('https://pro.fontawesome.com/releases/v5.10.0/css/all.css'); +@import url("https://fonts.googleapis.com/css?family=Rubik"); +@import url("https://pro.fontawesome.com/releases/v5.10.0/css/all.css"); -@import '~bootstrap/scss/bootstrap-utilities.scss'; -@import '~bootstrap/scss/bootstrap-grid.scss'; +@import "~bootstrap/scss/bootstrap-utilities.scss"; +@import "~bootstrap/scss/bootstrap-grid.scss"; // Include modular style files -@import 'animate.css'; -@import 'components'; -@import 'variables'; -@import 'utilities'; -@import 'widget'; -@import 'formik'; +@import "animate.css"; +@import "components"; +@import "variables"; +@import "utilities"; +@import "widget"; +@import "formik"; :root { // define colors - @include root-palette($palette); - - --text-on-dark: var(--pww-color-light-1000); - --text-on-light: var(--pww-color-dark-1000); - - --transition-duration: 0.15s; - - --border-radius: 4rem; - --box-shadow: 0 0 3rem rgba(var(--dark), .1); - --line-height: 1.5em; - - --container-padding: 2rem; - } - - // Fonts - html, - body { - font-family: 'Quattrocento'; - margin: 0; - } - - // default settings for the body - body { - font-size: var(--pww-font-size-4); - background-color: var(--pww-color-blue-100); - color: var(--pww-color-dark-1000); - } - - // Background Colors - .app { - min-height: 100vh; - display: flex; - flex-direction: column; - color: var(--pww-color-dark-1000); - font-weight: 100; - } \ No newline at end of file + @include root-palette($palette); + + --text-on-dark: var(--pww-color-light-1000); + --text-on-light: var(--pww-color-dark-1000); + + --transition-duration: 0.15s; + + --border-radius: 4rem; + --box-shadow: 0 0 3rem rgba(var(--dark), 0.1); + --line-height: 1.5em; + + --container-padding: 2rem; +} + +// Fonts +html, +body { + font-family: "Quattrocento"; + margin: 0; +} + +// default settings for the body +body { + font-size: var(--pww-font-size-4); + background-color: var(--pww-color-blue-100); + color: var(--pww-color-dark-1000); +} + +// Background Colors +.app { + min-height: 100vh; + display: flex; + flex-direction: column; + color: var(--pww-color-dark-1000); + font-weight: 100; +} diff --git a/app/tsconfig.json b/app/tsconfig.json index a273b0c..9d379a3 100644 --- a/app/tsconfig.json +++ b/app/tsconfig.json @@ -1,11 +1,7 @@ { "compilerOptions": { "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, @@ -20,7 +16,5 @@ "noEmit": true, "jsx": "react-jsx" }, - "include": [ - "src" - ] + "include": ["src"] } From 98da4e513ba38a2ce4c4007187681b276d22c0bf Mon Sep 17 00:00:00 2001 From: Lyton Date: Mon, 11 Nov 2024 09:54:06 -0600 Subject: [PATCH 27/27] =?UTF-8?q?=F0=9F=93=A8=20feat:=20implemented=20user?= =?UTF-8?q?=20invite=20with=20sendgrid=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: implemented user invite with sendgrid --- api/.env.example | 16 +++ api/assets/pww-logo.png | Bin 0 -> 22150 bytes api/package-lock.json | 212 ++++++++++++++++++++++++++++++++++++++-- api/package.json | 7 +- api/src/routes/user.ts | 29 ++++++ api/src/server.ts | 9 +- 6 files changed, 259 insertions(+), 14 deletions(-) create mode 100644 api/.env.example create mode 100644 api/assets/pww-logo.png diff --git a/api/.env.example b/api/.env.example new file mode 100644 index 0000000..b167564 --- /dev/null +++ b/api/.env.example @@ -0,0 +1,16 @@ +## copy this file and rename it to .env. Fill in the values from Notion + +# server +PORT=8000 + +# email service +SEND_GRID_API_KEY=##check notion## +SEND_GRID_TEST_EMAIL=##check notion## + +# database +MONGO_URI=##check notion## + +# backend auth +CLIENT_ORIGIN_URL=http://localhost:4040 +AUTH0_AUDIENCE=##check notion## +AUTH0_DOMAIN=##check notion## diff --git a/api/assets/pww-logo.png b/api/assets/pww-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c0f5a066c7ac226d9a665d8574c106dc83fb18e4 GIT binary patch literal 22150 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.*" + } + }, + "node_modules/@sendgrid/helpers": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-8.0.0.tgz", + "integrity": "sha512-Ze7WuW2Xzy5GT5WRx+yEv89fsg/pgy3T1E3FS0QEx0/VvRmigMZ5qyVGhJz4SxomegDkzXv/i0aFPpHKN8qdAA==", + "dependencies": { + "deepmerge": "^4.2.2" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/@sendgrid/mail": { + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-8.1.4.tgz", + "integrity": "sha512-MUpIZykD9ARie8LElYCqbcBhGGMaA/E6I7fEcG7Hc2An26QJyLtwOaKQ3taGp8xO8BICPJrSKuYV4bDeAJKFGQ==", + "dependencies": { + "@sendgrid/client": "^8.1.4", + "@sendgrid/helpers": "^8.0.0" + }, + "engines": { + "node": ">=12.*" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -153,11 +189,11 @@ "dev": true }, "node_modules/@types/node": { - "version": "22.5.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", - "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", + "version": "22.8.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.1.tgz", + "integrity": "sha512-k6Gi8Yyo8EtrNtkHXutUu2corfDf9su95VYVP10aGYMMROM6SAItZi0w1XszA6RtWTHSVp5OeFof37w0IEqCQg==", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.19.8" } }, "node_modules/@types/qs": { @@ -278,6 +314,21 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -400,6 +451,17 @@ "fsevents": "~2.3.2" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -462,6 +524,14 @@ "ms": "2.0.0" } }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -478,6 +548,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -636,6 +714,38 @@ "node": ">= 0.8" } }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -1274,6 +1384,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -1806,6 +1921,32 @@ "sparse-bitfield": "^3.0.3" } }, + "@sendgrid/client": { + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-8.1.4.tgz", + "integrity": "sha512-VxZoQ82MpxmjSXLR3ZAE2OWxvQIW2k2G24UeRPr/SYX8HqWLV/8UBN15T2WmjjnEb5XSmFImTJOKDzzSeKr9YQ==", + "requires": { + "@sendgrid/helpers": "^8.0.0", + "axios": "^1.7.4" + } + }, + "@sendgrid/helpers": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-8.0.0.tgz", + "integrity": "sha512-Ze7WuW2Xzy5GT5WRx+yEv89fsg/pgy3T1E3FS0QEx0/VvRmigMZ5qyVGhJz4SxomegDkzXv/i0aFPpHKN8qdAA==", + "requires": { + "deepmerge": "^4.2.2" + } + }, + "@sendgrid/mail": { + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-8.1.4.tgz", + "integrity": "sha512-MUpIZykD9ARie8LElYCqbcBhGGMaA/E6I7fEcG7Hc2An26QJyLtwOaKQ3taGp8xO8BICPJrSKuYV4bDeAJKFGQ==", + "requires": { + "@sendgrid/client": "^8.1.4", + "@sendgrid/helpers": "^8.0.0" + } + }, "@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -1891,11 +2032,11 @@ "dev": true }, "@types/node": { - "version": "22.5.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", - "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", + "version": "22.8.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.1.tgz", + "integrity": "sha512-k6Gi8Yyo8EtrNtkHXutUu2corfDf9su95VYVP10aGYMMROM6SAItZi0w1XszA6RtWTHSVp5OeFof37w0IEqCQg==", "requires": { - "undici-types": "~6.19.2" + "undici-types": "~6.19.8" } }, "@types/qs": { @@ -2001,6 +2142,21 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "requires": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2089,6 +2245,14 @@ "readdirp": "~3.6.0" } }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2139,6 +2303,11 @@ "ms": "2.0.0" } }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" + }, "define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -2149,6 +2318,11 @@ "gopd": "^1.0.1" } }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -2270,6 +2444,21 @@ "unpipe": "~1.0.0" } }, + "follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==" + }, + "form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -2661,6 +2850,11 @@ "ipaddr.js": "1.9.1" } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/api/package.json b/api/package.json index a4c2d8f..de25796 100644 --- a/api/package.json +++ b/api/package.json @@ -2,10 +2,10 @@ "name": "api", "version": "1.0.0", "description": "", - "main": "src/index.ts", + "main": "src/server.ts", "scripts": { "build": "tsc", - "start": "node src/index.ts", + "start": "nodemon ./dist/server.js", "dev": "nodemon ./src/server.ts", "test": "echo \"Error: no test specified\" && exit 1", "prettier": "prettier --single-quote --write 'src/**/*.{js,ts}'", @@ -15,6 +15,7 @@ "author": "", "license": "ISC", "dependencies": { + "@sendgrid/mail": "^8.1.4", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.21.0", @@ -26,7 +27,7 @@ "devDependencies": { "@types/cors": "^2.8.17", "@types/express": "^4.17.21", - "@types/node": "^22.5.4", + "@types/node": "^22.8.1", "@types/uuid": "^10.0.0", "body-parser": "^1.20.3", "prettier": "^3.3.3", diff --git a/api/src/routes/user.ts b/api/src/routes/user.ts index a734b81..ecfa60e 100644 --- a/api/src/routes/user.ts +++ b/api/src/routes/user.ts @@ -1,6 +1,7 @@ import express from "express"; import mongoose from "mongoose"; import dbConnect from "../config/db"; +import sgMail from "@sendgrid/mail"; const router = express.Router(); @@ -71,4 +72,32 @@ router.post("/test", async (req: any, res: any) => { return res.status(200).json({ name }); }); +router.post("/send-email", async (req: any, res: any) => { + try { + const SENDGRID_API_KEY = process.env.SEND_GRID_API_KEY || ""; + const SEND_GRID_TEST_EMAIL = process.env.SEND_GRID_TEST_EMAIL || ""; + + if (SENDGRID_API_KEY === "" || SEND_GRID_TEST_EMAIL === "") { + throw new Error("SendGrid API key or test email is missing"); + } + + const { email, name } = req.body; + + sgMail.setApiKey(SENDGRID_API_KEY); + + await sgMail.send({ + to: email, + from: SEND_GRID_TEST_EMAIL, + templateId: "d-7e26b82cf8624bafa4077b6ed73b52bf", + dynamicTemplateData: { + name: name, + }, + }); + + return res.status(200).json({ message: "Email successfully sent" }); + } catch (err) { + return res.status(400).json({ message: "Email sending failed" }); + } +}); + export default router; diff --git a/api/src/server.ts b/api/src/server.ts index cd7a639..c3f6ace 100644 --- a/api/src/server.ts +++ b/api/src/server.ts @@ -1,8 +1,11 @@ +import dotenv from "dotenv"; +dotenv.config({ path: path.resolve(__dirname, "../.env") }); + import express from "express"; import bodyParser from "body-parser"; import connectDB from "./config/db"; - import * as routes from "./routes/index"; +import path from "path"; var cors = require("cors"); @@ -16,4 +19,6 @@ app.use("/workshop", routes.workshop); connectDB(); -app.listen(process.env.PORT || 8000, () => console.log("Server running...")); +app.listen(process.env.PORT || 8000, () => + console.log(`Server running on port ${process.env.PORT || 8000}`), +);