From 02a5c5b15a601700042d4d328828a542c7b24f03 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Fri, 7 Oct 2022 17:42:53 -0400 Subject: [PATCH 01/23] Change hover color to be distinct from selection Students were getting confused and thinking they had selected an option when they were really just hovering on it. Also, touching the option results in it remaining "hovered" even after the finger is long gone. --- client/src/components/PollOptionButton.tsx | 8 +++----- client/tailwind.config.js | 3 ++- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/client/src/components/PollOptionButton.tsx b/client/src/components/PollOptionButton.tsx index 82b0b78..c14debe 100644 --- a/client/src/components/PollOptionButton.tsx +++ b/client/src/components/PollOptionButton.tsx @@ -18,11 +18,9 @@ export const PollOptionButton = ({ disabled={disabled} onClick={onClick} className={`m-2 py-2 px-40 inline-block ${ - selected ? "bg-hover" : "bg-primary" - } ${ - disabled - ? "cursor-not-allowed opacity-50" - : "hover:bg-hover cursor-pointer" + selected ? "bg-selected" : "bg-primary" + } ${disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"} ${ + !selected && !disabled ? "hover:bg-hover" : "" }`} >
{name}
diff --git a/client/tailwind.config.js b/client/tailwind.config.js index 52d469f..106c633 100644 --- a/client/tailwind.config.js +++ b/client/tailwind.config.js @@ -16,7 +16,8 @@ module.exports = { ...theme("colors"), primary: "#00204E", secondary: "#FFFFFF", - hover: "#00B5B5", + hover: "#0060de", + selected: "#00B5B5", background: "#F5F5F5", }), }, From a805ba845b250722f06411a54acc4decbc61cea4 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Fri, 7 Oct 2022 17:44:27 -0400 Subject: [PATCH 02/23] Explicitly state when no option is selected on voting page --- client/src/pages/VotePage.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/src/pages/VotePage.tsx b/client/src/pages/VotePage.tsx index 5e5f4ae..31aed97 100644 --- a/client/src/pages/VotePage.tsx +++ b/client/src/pages/VotePage.tsx @@ -110,7 +110,11 @@ export const VotePage = () => { ) : (
-
+
0 ? selectedOption : "None" + }`} + />
{optionButtons()}
); From 60557a27538da43d5fea4ad29a877aeaf412bbfc Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Fri, 7 Oct 2022 17:44:55 -0400 Subject: [PATCH 03/23] Send a browser notification when a new question starts This is done in addition to changing the title as was done previously. --- client/src/pages/VotePage.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/client/src/pages/VotePage.tsx b/client/src/pages/VotePage.tsx index 31aed97..a80089f 100644 --- a/client/src/pages/VotePage.tsx +++ b/client/src/pages/VotePage.tsx @@ -15,6 +15,7 @@ export const VotePage = () => { const cookies = new Cookies(); const [pollCode] = useState(cookies.get(pollCodeCookie)); const [started, setStarted] = useState(false); + const [notifPermission, setNotifPermission] = useState("denied"); const [errorCode, setErrorCode] = useState(0); const [selectedOption, setSelectionOption] = useState(""); @@ -31,6 +32,11 @@ export const VotePage = () => { useEffect(() => { socket.emit("join", pollCode); + try { + Notification.requestPermission().then(setNotifPermission); + } catch (e) { + /* notifications probably not supported */ + } window.addEventListener("blur", onBlur); window.addEventListener("focus", onFocus); return () => { @@ -59,6 +65,13 @@ export const VotePage = () => { setSelectionOption(""); if (!isFocus) { document.title = questionStarted; + if (notifPermission === "granted") { + /* send notification */ + const notification = new Notification("New Question Started!", { + icon: "/favicon.ico", + tag: "new-question", + }); + } } } }; @@ -82,7 +95,7 @@ export const VotePage = () => { socket.off("end", pollClosedHandler); socket.off("ack", voteAckHandler); }; - }, [errorCode, started, selectedOption]); + }, [errorCode, started, selectedOption, notifPermission]); const pollButtonHandler = (selectedOption: string) => { socket.emit("vote", (selectedOption.charCodeAt(0) % 65) + 1); From 8954af67fec4f66067d9ca9f1fe067a3d5152b6d Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Fri, 7 Oct 2022 17:45:34 -0400 Subject: [PATCH 04/23] Fix a typo in the README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0fd17ca..bb97df7 100644 --- a/README.md +++ b/README.md @@ -205,7 +205,7 @@ utorid3 2. Setting up the server `.env` file (Placed in the root of your `server` folder) ``` -PPORT=3001 +PORT=3001 MONGODB_URL="mongodb://localhost:27018/quiz" FRONTEND_URL="http://localhost:3000" REDIS_URL="redis://default:password@localhost:6379" From b18d2db7388c72a8e1f2d8dc26c168e48ef59e7e Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sun, 9 Oct 2022 07:46:55 -0400 Subject: [PATCH 05/23] Revert hover color and choose a new selected color --- client/tailwind.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/tailwind.config.js b/client/tailwind.config.js index 106c633..5abea22 100644 --- a/client/tailwind.config.js +++ b/client/tailwind.config.js @@ -16,8 +16,8 @@ module.exports = { ...theme("colors"), primary: "#00204E", secondary: "#FFFFFF", - hover: "#0060de", - selected: "#00B5B5", + hover: "#00B5B5", + selected: "#0171B7", background: "#F5F5F5", }), }, From 8d177c120174a2ff6bb5b3bbc9ecb06eb68a9861 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sun, 9 Oct 2022 07:52:18 -0400 Subject: [PATCH 06/23] Refactor notification permission state --- client/src/pages/VotePage.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/client/src/pages/VotePage.tsx b/client/src/pages/VotePage.tsx index a80089f..2f13a42 100644 --- a/client/src/pages/VotePage.tsx +++ b/client/src/pages/VotePage.tsx @@ -15,7 +15,7 @@ export const VotePage = () => { const cookies = new Cookies(); const [pollCode] = useState(cookies.get(pollCodeCookie)); const [started, setStarted] = useState(false); - const [notifPermission, setNotifPermission] = useState("denied"); + const [hasAllowedNotif, setAllowedNotif] = useState(false); const [errorCode, setErrorCode] = useState(0); const [selectedOption, setSelectionOption] = useState(""); @@ -33,7 +33,9 @@ export const VotePage = () => { useEffect(() => { socket.emit("join", pollCode); try { - Notification.requestPermission().then(setNotifPermission); + Notification.requestPermission().then((permission) => + setAllowedNotif(permission === "granted") + ); } catch (e) { /* notifications probably not supported */ } @@ -65,7 +67,7 @@ export const VotePage = () => { setSelectionOption(""); if (!isFocus) { document.title = questionStarted; - if (notifPermission === "granted") { + if (hasAllowedNotif) { /* send notification */ const notification = new Notification("New Question Started!", { icon: "/favicon.ico", @@ -95,7 +97,7 @@ export const VotePage = () => { socket.off("end", pollClosedHandler); socket.off("ack", voteAckHandler); }; - }, [errorCode, started, selectedOption, notifPermission]); + }, [errorCode, started, selectedOption, hasAllowedNotif]); const pollButtonHandler = (selectedOption: string) => { socket.emit("vote", (selectedOption.charCodeAt(0) % 65) + 1); From e10b80f17ad204ef7611e2da0f6d6587e750dcf2 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sun, 9 Oct 2022 09:50:01 -0400 Subject: [PATCH 07/23] Refactor page focus/notification system * Use document.visibilityState to determine when the tab is not focused. This avoids a race condition where the tab is unfocused before the page has loaded, so the app can't detect it. Also, this lets us get rid of an event handler. * Forcefully close the notification that was previously open, to improve reliability of seeing the new notification. --- client/src/pages/VotePage.tsx | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/client/src/pages/VotePage.tsx b/client/src/pages/VotePage.tsx index 2f13a42..991b9d7 100644 --- a/client/src/pages/VotePage.tsx +++ b/client/src/pages/VotePage.tsx @@ -16,18 +16,13 @@ export const VotePage = () => { const [pollCode] = useState(cookies.get(pollCodeCookie)); const [started, setStarted] = useState(false); const [hasAllowedNotif, setAllowedNotif] = useState(false); + const [lastNotif, setLastNotif] = useState(null); const [errorCode, setErrorCode] = useState(0); const [selectedOption, setSelectionOption] = useState(""); - const [isFocus, setFocus] = useState(true); - - const onBlur = () => { - setFocus(false); - }; const onFocus = () => { - document.title = mcsPollVoting; - setFocus(true); + if (document.visibilityState === "visible") document.title = mcsPollVoting; }; useEffect(() => { @@ -39,11 +34,9 @@ export const VotePage = () => { } catch (e) { /* notifications probably not supported */ } - window.addEventListener("blur", onBlur); - window.addEventListener("focus", onFocus); + document.addEventListener("visibilitychange", onFocus); return () => { - window.removeEventListener("blur", onBlur); - window.removeEventListener("focus", onFocus); + document.removeEventListener("visibilitychange", onFocus); }; }, []); @@ -65,14 +58,17 @@ export const VotePage = () => { const audio = new Audio("/newQuestion.wav"); audio.play(); setSelectionOption(""); - if (!isFocus) { + if (document.visibilityState === "hidden") { document.title = questionStarted; if (hasAllowedNotif) { - /* send notification */ + /* first, close old notification to prevent spam */ + if (lastNotif != null) lastNotif.close(); + /* send new notification */ const notification = new Notification("New Question Started!", { icon: "/favicon.ico", tag: "new-question", }); + setLastNotif(notification); } } } @@ -88,6 +84,10 @@ export const VotePage = () => { const voteAckHandler = (data: any) => { setSelectionOption(String.fromCharCode(data + 64)); + if (lastNotif != null) { + lastNotif.close(); + setLastNotif(null); + } }; socket.on("ack", voteAckHandler); @@ -97,7 +97,7 @@ export const VotePage = () => { socket.off("end", pollClosedHandler); socket.off("ack", voteAckHandler); }; - }, [errorCode, started, selectedOption, hasAllowedNotif]); + }, [errorCode, started, selectedOption, hasAllowedNotif, lastNotif]); const pollButtonHandler = (selectedOption: string) => { socket.emit("vote", (selectedOption.charCodeAt(0) % 65) + 1); From 47b2e0a6aa0b2f7e9496b54d6ecd3d499d6a239c Mon Sep 17 00:00:00 2001 From: Ido Date: Mon, 10 Oct 2022 18:01:17 -0400 Subject: [PATCH 08/23] Added MUI --- client/package.json | 3 + client/yarn.lock | 366 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 361 insertions(+), 8 deletions(-) diff --git a/client/package.json b/client/package.json index ddb392a..e093cdd 100644 --- a/client/package.json +++ b/client/package.json @@ -4,6 +4,9 @@ "private": true, "dependencies": { "@craco/craco": "^6.3.0", + "@emotion/react": "^11.10.4", + "@emotion/styled": "^11.10.4", + "@mui/material": "^5.10.8", "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", diff --git a/client/yarn.lock b/client/yarn.lock index 10f0cc4..477d098 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -183,6 +183,13 @@ dependencies: "@babel/types" "^7.15.4" +"@babel/helper-module-imports@^7.16.7": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" + integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== + dependencies: + "@babel/types" "^7.18.6" + "@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.14.5", "@babel/helper-module-transforms@^7.15.4", "@babel/helper-module-transforms@^7.15.8": version "7.15.8" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.15.8.tgz#d8c0e75a87a52e374a8f25f855174786a09498b2" @@ -209,6 +216,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9" integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ== +"@babel/helper-plugin-utils@^7.18.6": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf" + integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== + "@babel/helper-remap-async-to-generator@^7.14.5", "@babel/helper-remap-async-to-generator@^7.15.4": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.15.4.tgz#2637c0731e4c90fbf58ac58b50b2b5a192fc970f" @@ -249,11 +261,21 @@ dependencies: "@babel/types" "^7.15.4" +"@babel/helper-string-parser@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" + integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== + "@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.9", "@babel/helper-validator-identifier@^7.15.7": version "7.15.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== +"@babel/helper-validator-identifier@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== + "@babel/helper-validator-option@^7.12.1", "@babel/helper-validator-option@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" @@ -548,6 +570,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" +"@babel/plugin-syntax-jsx@^7.17.12": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0" + integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" @@ -1164,6 +1193,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.18.3", "@babel/runtime@^7.19.0", "@babel/runtime@^7.8.7": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.4.tgz#a42f814502ee467d55b38dd1c256f53a7b885c78" + integrity sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.10.4", "@babel/template@^7.15.4", "@babel/template@^7.3.3": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.15.4.tgz#51898d35dcf3faa670c4ee6afcfd517ee139f194" @@ -1196,6 +1232,15 @@ "@babel/helper-validator-identifier" "^7.14.9" to-fast-properties "^2.0.0" +"@babel/types@^7.18.6": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.4.tgz#0dd5c91c573a202d600490a35b33246fed8a41c7" + integrity sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw== + dependencies: + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -1229,6 +1274,114 @@ resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18" integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg== +"@emotion/babel-plugin@^11.10.0": + version "11.10.2" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.10.2.tgz#879db80ba622b3f6076917a1e6f648b1c7d008c7" + integrity sha512-xNQ57njWTFVfPAc3cjfuaPdsgLp5QOSuRsj9MA6ndEhH/AzuZM86qIQzt6rq+aGBwj3n5/TkLmU5lhAfdRmogA== + dependencies: + "@babel/helper-module-imports" "^7.16.7" + "@babel/plugin-syntax-jsx" "^7.17.12" + "@babel/runtime" "^7.18.3" + "@emotion/hash" "^0.9.0" + "@emotion/memoize" "^0.8.0" + "@emotion/serialize" "^1.1.0" + babel-plugin-macros "^3.1.0" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "4.0.13" + +"@emotion/cache@^11.10.0", "@emotion/cache@^11.10.3": + version "11.10.3" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.10.3.tgz#c4f67904fad10c945fea5165c3a5a0583c164b87" + integrity sha512-Psmp/7ovAa8appWh3g51goxu/z3iVms7JXOreq136D8Bbn6dYraPnmL6mdM8GThEx9vwSn92Fz+mGSjBzN8UPQ== + dependencies: + "@emotion/memoize" "^0.8.0" + "@emotion/sheet" "^1.2.0" + "@emotion/utils" "^1.2.0" + "@emotion/weak-memoize" "^0.3.0" + stylis "4.0.13" + +"@emotion/hash@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.0.tgz#c5153d50401ee3c027a57a177bc269b16d889cb7" + integrity sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ== + +"@emotion/is-prop-valid@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz#7f2d35c97891669f7e276eb71c83376a5dc44c83" + integrity sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg== + dependencies: + "@emotion/memoize" "^0.8.0" + +"@emotion/memoize@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.0.tgz#f580f9beb67176fa57aae70b08ed510e1b18980f" + integrity sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA== + +"@emotion/react@^11.10.4": + version "11.10.4" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.10.4.tgz#9dc6bccbda5d70ff68fdb204746c0e8b13a79199" + integrity sha512-j0AkMpr6BL8gldJZ6XQsQ8DnS9TxEQu1R+OGmDZiWjBAJtCcbt0tS3I/YffoqHXxH6MjgI7KdMbYKw3MEiU9eA== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.10.0" + "@emotion/cache" "^11.10.0" + "@emotion/serialize" "^1.1.0" + "@emotion/use-insertion-effect-with-fallbacks" "^1.0.0" + "@emotion/utils" "^1.2.0" + "@emotion/weak-memoize" "^0.3.0" + hoist-non-react-statics "^3.3.1" + +"@emotion/serialize@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.0.tgz#b1f97b1011b09346a40e9796c37a3397b4ea8ea8" + integrity sha512-F1ZZZW51T/fx+wKbVlwsfchr5q97iW8brAnXmsskz4d0hVB4O3M/SiA3SaeH06x02lSNzkkQv+n3AX3kCXKSFA== + dependencies: + "@emotion/hash" "^0.9.0" + "@emotion/memoize" "^0.8.0" + "@emotion/unitless" "^0.8.0" + "@emotion/utils" "^1.2.0" + csstype "^3.0.2" + +"@emotion/sheet@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.0.tgz#771b1987855839e214fc1741bde43089397f7be5" + integrity sha512-OiTkRgpxescko+M51tZsMq7Puu/KP55wMT8BgpcXVG2hqXc0Vo0mfymJ/Uj24Hp0i083ji/o0aLddh08UEjq8w== + +"@emotion/styled@^11.10.4": + version "11.10.4" + resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.10.4.tgz#e93f84a4d54003c2acbde178c3f97b421fce1cd4" + integrity sha512-pRl4R8Ez3UXvOPfc2bzIoV8u9P97UedgHS4FPX594ntwEuAMA114wlaHvOK24HB48uqfXiGlYIZYCxVJ1R1ttQ== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.10.0" + "@emotion/is-prop-valid" "^1.2.0" + "@emotion/serialize" "^1.1.0" + "@emotion/use-insertion-effect-with-fallbacks" "^1.0.0" + "@emotion/utils" "^1.2.0" + +"@emotion/unitless@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.0.tgz#a4a36e9cbdc6903737cd20d38033241e1b8833db" + integrity sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw== + +"@emotion/use-insertion-effect-with-fallbacks@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz#ffadaec35dbb7885bd54de3fa267ab2f860294df" + integrity sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A== + +"@emotion/utils@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.0.tgz#9716eaccbc6b5ded2ea5a90d65562609aab0f561" + integrity sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw== + +"@emotion/weak-memoize@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz#ea89004119dc42db2e1dba0f97d553f7372f6fcb" + integrity sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg== + "@eslint/eslintrc@^0.4.3": version "0.4.3" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" @@ -1493,6 +1646,92 @@ "@types/yargs" "^16.0.0" chalk "^4.0.0" +"@mui/base@5.0.0-alpha.100": + version "5.0.0-alpha.100" + resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-alpha.100.tgz#5587c98fb8c6fdf9138b92c519fe3fd79682f7ac" + integrity sha512-bSoJEKCENtmJrJDECHUe9PiqztIUACuSskyqw9ypqE7Dz3WxL3e8puFsWBkUsz+WOCjXh4B4Xljn88Ucxxv5HA== + dependencies: + "@babel/runtime" "^7.19.0" + "@emotion/is-prop-valid" "^1.2.0" + "@mui/types" "^7.2.0" + "@mui/utils" "^5.10.6" + "@popperjs/core" "^2.11.6" + clsx "^1.2.1" + prop-types "^15.8.1" + react-is "^18.2.0" + +"@mui/core-downloads-tracker@^5.10.8": + version "5.10.8" + resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.10.8.tgz#b00402dd0796e7e99a09cfc23cb0b0a6196c8e09" + integrity sha512-V5D7OInO4P9PdT/JACg7fwjbOORm3GklaMVgdGomjyxiyetgRND5CC9r35e1LK/DqHdoyDuhbFzdfrqWtpmEIw== + +"@mui/material@^5.10.8": + version "5.10.8" + resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.10.8.tgz#a223ec5f1128515107381e875f8d6257a00c3cd3" + integrity sha512-sF/Ka0IJjGXV52zoT4xAWEqXVRjNYbIjATo9L4Q5oQC5iJpGrKJFY16uNtWWB0+vp/nayAuPGZHrxtV+t3ecdQ== + dependencies: + "@babel/runtime" "^7.19.0" + "@mui/base" "5.0.0-alpha.100" + "@mui/core-downloads-tracker" "^5.10.8" + "@mui/system" "^5.10.8" + "@mui/types" "^7.2.0" + "@mui/utils" "^5.10.6" + "@types/react-transition-group" "^4.4.5" + clsx "^1.2.1" + csstype "^3.1.1" + prop-types "^15.8.1" + react-is "^18.2.0" + react-transition-group "^4.4.5" + +"@mui/private-theming@^5.10.6": + version "5.10.6" + resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.10.6.tgz#2c6bb2a4b7034cd402a099bd0349f217584e7b25" + integrity sha512-I/W0QyTLRdEx6py3lKAquKO/rNF/7j+nIOM/xCyI9kU0fcotVTcTY08mKMsS6vrzdWpi6pAkD0wP0KwWy5R5VA== + dependencies: + "@babel/runtime" "^7.19.0" + "@mui/utils" "^5.10.6" + prop-types "^15.8.1" + +"@mui/styled-engine@^5.10.8": + version "5.10.8" + resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.10.8.tgz#2db411e4278f06f70ccb6b5cd56ace67109513f6" + integrity sha512-w+y8WI18EJV6zM/q41ug19cE70JTeO6sWFsQ7tgePQFpy6ToCVPh0YLrtqxUZXSoMStW5FMw0t9fHTFAqPbngw== + dependencies: + "@babel/runtime" "^7.19.0" + "@emotion/cache" "^11.10.3" + csstype "^3.1.1" + prop-types "^15.8.1" + +"@mui/system@^5.10.8": + version "5.10.8" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.10.8.tgz#afea52aeed34bd2d98c993322b5b09585106953d" + integrity sha512-hRQ354zcrYP/KHqK8FheICSvE9raQaUgQaV+A3oD4JETaFUCVI9Ytt+RcQYgTqx02xlCXIjl8LK1rPjTneySqw== + dependencies: + "@babel/runtime" "^7.19.0" + "@mui/private-theming" "^5.10.6" + "@mui/styled-engine" "^5.10.8" + "@mui/types" "^7.2.0" + "@mui/utils" "^5.10.6" + clsx "^1.2.1" + csstype "^3.1.1" + prop-types "^15.8.1" + +"@mui/types@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.0.tgz#91380c2d42420f51f404120f7a9270eadd6f5c23" + integrity sha512-lGXtFKe5lp3UxTBGqKI1l7G8sE2xBik8qCfrLHD5olwP/YU0/ReWoWT7Lp1//ri32dK39oPMrJN8TgbkCSbsNA== + +"@mui/utils@^5.10.6": + version "5.10.6" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.10.6.tgz#98d432d2b05544c46efe356cf095cea3a37c2e59" + integrity sha512-g0Qs8xN/MW2M3fLL8197h5J2VB9U+49fLlnKKqC6zy/yus5cZwdT+Gwec+wUMxgwQoxMDn+J8oDWAn28kEOR/Q== + dependencies: + "@babel/runtime" "^7.19.0" + "@types/prop-types" "^15.7.5" + "@types/react-is" "^16.7.1 || ^17.0.0" + prop-types "^15.8.1" + react-is "^18.2.0" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -1542,6 +1781,11 @@ schema-utils "^2.6.5" source-map "^0.7.3" +"@popperjs/core@^2.11.6": + version "2.11.6" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45" + integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw== + "@popperjs/core@^2.9.2": version "2.10.2" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.10.2.tgz#0798c03351f0dea1a5a4cabddf26a55a7cbee590" @@ -1937,9 +2181,9 @@ integrity sha512-8MLkBIYQMuhRBQzGN9875bYsOhPnf/0rgXGo66S2FemHkhbn9qtsz9ywV1iCG+vbjigE4WUNVvw37Dx+L0qsPg== "@types/node@^12.0.0": - version "12.20.33" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.33.tgz#24927446e8b7669d10abacedd16077359678f436" - integrity sha512-5XmYX2GECSa+CxMYaFsr2mrql71Q4EvHjKS+ox/SiwSdaASMoBIWE6UmZqFO+VX1jIcsYLStI4FFoB6V7FeIYw== + version "12.20.55" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" + integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -1961,6 +2205,11 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== +"@types/prop-types@^15.7.5": + version "15.7.5" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" + integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== + "@types/q@^1.5.1": version "1.5.5" resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.5.tgz#75a2a8e7d8ab4b230414505d92335d1dcb53a6df" @@ -1997,6 +2246,13 @@ dependencies: "@types/react" "*" +"@types/react-is@^16.7.1 || ^17.0.0": + version "17.0.3" + resolved "https://registry.yarnpkg.com/@types/react-is/-/react-is-17.0.3.tgz#2d855ba575f2fc8d17ef9861f084acc4b90a137a" + integrity sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw== + dependencies: + "@types/react" "*" + "@types/react-router-dom@^5.3.1": version "5.3.1" resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.1.tgz#76700ccce6529413ec723024b71f01fc77a4a980" @@ -2014,6 +2270,13 @@ "@types/history" "*" "@types/react" "*" +"@types/react-transition-group@^4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.5.tgz#aae20dcf773c5aa275d5b9f7cdbca638abc5e416" + integrity sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA== + dependencies: + "@types/react" "*" + "@types/react@*", "@types/react@^17.0.0": version "17.0.30" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.30.tgz#2f8e6f5ab6415c091cc5e571942ee9064b17609e" @@ -2923,6 +3186,15 @@ babel-plugin-macros@2.8.0: cosmiconfig "^6.0.0" resolve "^1.12.0" +babel-plugin-macros@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" + integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== + dependencies: + "@babel/runtime" "^7.12.5" + cosmiconfig "^7.0.0" + resolve "^1.19.0" + babel-plugin-named-asset-import@^0.3.7: version "0.3.7" resolved "https://registry.yarnpkg.com/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.7.tgz#156cd55d3f1228a5765774340937afc8398067dd" @@ -3631,6 +3903,11 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" +clsx@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" + integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -3846,6 +4123,11 @@ convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: dependencies: safe-buffer "~5.1.1" +convert-source-map@^1.5.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -4264,6 +4546,11 @@ csstype@^3.0.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.9.tgz#6410af31b26bd0520933d02cbc64fce9ce3fbf0b" integrity sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw== +csstype@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9" + integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== + cyclist@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" @@ -4546,6 +4833,14 @@ dom-converter@^0.2.0: dependencies: utila "~0.4" +dom-helpers@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + dom-serializer@0: version "0.2.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" @@ -5484,6 +5779,11 @@ find-cache-dir@^3.3.1: make-dir "^3.0.2" pkg-dir "^4.1.0" +find-root@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" + integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== + find-up@4.1.0, find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -5975,7 +6275,7 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.1.0: +hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.1: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -6463,6 +6763,13 @@ is-core-module@^2.0.0, is-core-module@^2.2.0, is-core-module@^2.7.0: dependencies: has "^1.0.3" +is-core-module@^2.9.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed" + integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== + dependencies: + has "^1.0.3" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -8604,7 +8911,7 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-parse@^1.0.6: +path-parse@^1.0.6, path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== @@ -9571,6 +9878,15 @@ prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2: object-assign "^4.1.1" react-is "^16.8.1" +prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + proxy-addr@~2.0.5: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -9827,7 +10143,7 @@ react-fast-compare@^3.0.1: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== -react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: +react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -9837,6 +10153,11 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== +react-is@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + react-onclickoutside@^6.12.0: version "6.12.1" resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.12.1.tgz#92dddd28f55e483a1838c5c2930e051168c1e96b" @@ -9950,6 +10271,16 @@ react-scripts@4.0.3: optionalDependencies: fsevents "^2.1.3" +react-transition-group@^4.4.5: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" @@ -10239,6 +10570,15 @@ resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.1 is-core-module "^2.2.0" path-parse "^1.0.6" +resolve@^1.19.0: + version "1.22.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + resolve@^2.0.0-next.3: version "2.0.0-next.3" resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.3.tgz#d41016293d4a8586a39ca5d9b5f15cbea1f55e46" @@ -10822,10 +11162,10 @@ source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, sourc resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@^0.5.0, source-map@^0.5.6: +source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== source-map@^0.7.3, source-map@~0.7.2: version "0.7.3" @@ -11153,6 +11493,11 @@ stylehacks@^4.0.0: postcss "^7.0.0" postcss-selector-parser "^3.0.0" +stylis@4.0.13: + version "4.0.13" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91" + integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag== + supports-color@8.1.1: version "8.1.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" @@ -11189,6 +11534,11 @@ supports-hyperlinks@^2.0.0: has-flag "^4.0.0" supports-color "^7.0.0" +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + svg-parser@^2.0.2: version "2.0.4" resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" From fbba2ba97e4890ff9e063365eb133946c79b7c89 Mon Sep 17 00:00:00 2001 From: Ido Date: Mon, 10 Oct 2022 18:03:08 -0400 Subject: [PATCH 09/23] Added loading wheel (see #73) --- client/src/pages/VotePage.tsx | 63 +++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/client/src/pages/VotePage.tsx b/client/src/pages/VotePage.tsx index 5e5f4ae..93aa52a 100644 --- a/client/src/pages/VotePage.tsx +++ b/client/src/pages/VotePage.tsx @@ -9,12 +9,20 @@ import { questionStarted, } from "../constants/constants"; import { socket } from "../socket"; +import Alert from "@mui/material/Alert"; +import Snackbar from "@mui/material/Snackbar"; +import CircularProgress from "@mui/material/CircularProgress"; export const VotePage = () => { const history = useHistory(); const cookies = new Cookies(); const [pollCode] = useState(cookies.get(pollCodeCookie)); - const [started, setStarted] = useState(false); + // const [started, setStarted] = useState(false); + const [started, setStarted] = useState(true); + + const [loading, setLoading] = useState(false); + const [timeOutError, setTimeOutError] = useState(false); + const [timeOutCode, setTimeOutCode] = useState(0); const [errorCode, setErrorCode] = useState(0); const [selectedOption, setSelectionOption] = useState(""); @@ -73,6 +81,9 @@ export const VotePage = () => { const voteAckHandler = (data: any) => { setSelectionOption(String.fromCharCode(data + 64)); + clearTimeout(timeOutCode); + setTimeOutError(false); + setLoading(false); }; socket.on("ack", voteAckHandler); @@ -85,9 +96,25 @@ export const VotePage = () => { }, [errorCode, started, selectedOption]); const pollButtonHandler = (selectedOption: string) => { + setLoading(true); + setTimeOutCode( + setTimeout(() => { + setTimeOutError(true); + setLoading(false); + }, 10000) as unknown as number + ); socket.emit("vote", (selectedOption.charCodeAt(0) % 65) + 1); }; + const handleErrorClose = ( + e: React.SyntheticEvent | Event, + reason?: string + ) => { + if (reason === "clickaway") { + return; + } + setTimeOutError(false); + }; const optionButtons = () => { const pollOptionButtons = []; for (let i = 65; i < 70; i++) { @@ -111,7 +138,39 @@ export const VotePage = () => {
-
{optionButtons()}
+
+
{optionButtons()}
+ {loading ? ( +
+
+ +
+
+ ) : null} +
+ + + Vote not received, please try again! + +
); }; From 2a094e7dec4e24ccc7d6b0bfc27915914bc164aa Mon Sep 17 00:00:00 2001 From: Ido Date: Mon, 10 Oct 2022 18:09:09 -0400 Subject: [PATCH 10/23] Removed debug code --- client/src/pages/VotePage.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/src/pages/VotePage.tsx b/client/src/pages/VotePage.tsx index 93aa52a..fa9ca09 100644 --- a/client/src/pages/VotePage.tsx +++ b/client/src/pages/VotePage.tsx @@ -17,8 +17,7 @@ export const VotePage = () => { const history = useHistory(); const cookies = new Cookies(); const [pollCode] = useState(cookies.get(pollCodeCookie)); - // const [started, setStarted] = useState(false); - const [started, setStarted] = useState(true); + const [started, setStarted] = useState(false); const [loading, setLoading] = useState(false); const [timeOutError, setTimeOutError] = useState(false); From 4c4321eea15935c700712b7ad6a02933ebda0d14 Mon Sep 17 00:00:00 2001 From: Ido Date: Wed, 12 Oct 2022 17:39:37 -0400 Subject: [PATCH 11/23] Removed MUI --- client/package.json | 3 - client/src/pages/VotePage.tsx | 53 +++-- client/yarn.lock | 358 +--------------------------------- 3 files changed, 26 insertions(+), 388 deletions(-) diff --git a/client/package.json b/client/package.json index e093cdd..ddb392a 100644 --- a/client/package.json +++ b/client/package.json @@ -4,9 +4,6 @@ "private": true, "dependencies": { "@craco/craco": "^6.3.0", - "@emotion/react": "^11.10.4", - "@emotion/styled": "^11.10.4", - "@mui/material": "^5.10.8", "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", diff --git a/client/src/pages/VotePage.tsx b/client/src/pages/VotePage.tsx index fa9ca09..f9fded7 100644 --- a/client/src/pages/VotePage.tsx +++ b/client/src/pages/VotePage.tsx @@ -20,7 +20,6 @@ export const VotePage = () => { const [started, setStarted] = useState(false); const [loading, setLoading] = useState(false); - const [timeOutError, setTimeOutError] = useState(false); const [timeOutCode, setTimeOutCode] = useState(0); const [errorCode, setErrorCode] = useState(0); @@ -81,7 +80,6 @@ export const VotePage = () => { const voteAckHandler = (data: any) => { setSelectionOption(String.fromCharCode(data + 64)); clearTimeout(timeOutCode); - setTimeOutError(false); setLoading(false); }; socket.on("ack", voteAckHandler); @@ -98,21 +96,14 @@ export const VotePage = () => { setLoading(true); setTimeOutCode( setTimeout(() => { - setTimeOutError(true); + triggerTimeOutError(); setLoading(false); }, 10000) as unknown as number ); socket.emit("vote", (selectedOption.charCodeAt(0) % 65) + 1); }; - - const handleErrorClose = ( - e: React.SyntheticEvent | Event, - reason?: string - ) => { - if (reason === "clickaway") { - return; - } - setTimeOutError(false); + const triggerTimeOutError = () => { + alert("Vote not received, please try again!"); }; const optionButtons = () => { const pollOptionButtons = []; @@ -147,29 +138,29 @@ export const VotePage = () => { } style={{ backgroundColor: "rgba(0,0,0,0.5)" }} > - +
+ + Loading... +
) : null} - - - Vote not received, please try again! - - ); }; diff --git a/client/yarn.lock b/client/yarn.lock index 477d098..a8c1f23 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -183,13 +183,6 @@ dependencies: "@babel/types" "^7.15.4" -"@babel/helper-module-imports@^7.16.7": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" - integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== - dependencies: - "@babel/types" "^7.18.6" - "@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.14.5", "@babel/helper-module-transforms@^7.15.4", "@babel/helper-module-transforms@^7.15.8": version "7.15.8" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.15.8.tgz#d8c0e75a87a52e374a8f25f855174786a09498b2" @@ -216,11 +209,6 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9" integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ== -"@babel/helper-plugin-utils@^7.18.6": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf" - integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== - "@babel/helper-remap-async-to-generator@^7.14.5", "@babel/helper-remap-async-to-generator@^7.15.4": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.15.4.tgz#2637c0731e4c90fbf58ac58b50b2b5a192fc970f" @@ -261,21 +249,11 @@ dependencies: "@babel/types" "^7.15.4" -"@babel/helper-string-parser@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" - integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== - "@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.9", "@babel/helper-validator-identifier@^7.15.7": version "7.15.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== -"@babel/helper-validator-identifier@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" - integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== - "@babel/helper-validator-option@^7.12.1", "@babel/helper-validator-option@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" @@ -570,13 +548,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-jsx@^7.17.12": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0" - integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" @@ -1193,13 +1164,6 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.18.3", "@babel/runtime@^7.19.0", "@babel/runtime@^7.8.7": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.4.tgz#a42f814502ee467d55b38dd1c256f53a7b885c78" - integrity sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA== - dependencies: - regenerator-runtime "^0.13.4" - "@babel/template@^7.10.4", "@babel/template@^7.15.4", "@babel/template@^7.3.3": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.15.4.tgz#51898d35dcf3faa670c4ee6afcfd517ee139f194" @@ -1232,15 +1196,6 @@ "@babel/helper-validator-identifier" "^7.14.9" to-fast-properties "^2.0.0" -"@babel/types@^7.18.6": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.4.tgz#0dd5c91c573a202d600490a35b33246fed8a41c7" - integrity sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw== - dependencies: - "@babel/helper-string-parser" "^7.19.4" - "@babel/helper-validator-identifier" "^7.19.1" - to-fast-properties "^2.0.0" - "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -1274,114 +1229,6 @@ resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18" integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg== -"@emotion/babel-plugin@^11.10.0": - version "11.10.2" - resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.10.2.tgz#879db80ba622b3f6076917a1e6f648b1c7d008c7" - integrity sha512-xNQ57njWTFVfPAc3cjfuaPdsgLp5QOSuRsj9MA6ndEhH/AzuZM86qIQzt6rq+aGBwj3n5/TkLmU5lhAfdRmogA== - dependencies: - "@babel/helper-module-imports" "^7.16.7" - "@babel/plugin-syntax-jsx" "^7.17.12" - "@babel/runtime" "^7.18.3" - "@emotion/hash" "^0.9.0" - "@emotion/memoize" "^0.8.0" - "@emotion/serialize" "^1.1.0" - babel-plugin-macros "^3.1.0" - convert-source-map "^1.5.0" - escape-string-regexp "^4.0.0" - find-root "^1.1.0" - source-map "^0.5.7" - stylis "4.0.13" - -"@emotion/cache@^11.10.0", "@emotion/cache@^11.10.3": - version "11.10.3" - resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.10.3.tgz#c4f67904fad10c945fea5165c3a5a0583c164b87" - integrity sha512-Psmp/7ovAa8appWh3g51goxu/z3iVms7JXOreq136D8Bbn6dYraPnmL6mdM8GThEx9vwSn92Fz+mGSjBzN8UPQ== - dependencies: - "@emotion/memoize" "^0.8.0" - "@emotion/sheet" "^1.2.0" - "@emotion/utils" "^1.2.0" - "@emotion/weak-memoize" "^0.3.0" - stylis "4.0.13" - -"@emotion/hash@^0.9.0": - version "0.9.0" - resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.0.tgz#c5153d50401ee3c027a57a177bc269b16d889cb7" - integrity sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ== - -"@emotion/is-prop-valid@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz#7f2d35c97891669f7e276eb71c83376a5dc44c83" - integrity sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg== - dependencies: - "@emotion/memoize" "^0.8.0" - -"@emotion/memoize@^0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.0.tgz#f580f9beb67176fa57aae70b08ed510e1b18980f" - integrity sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA== - -"@emotion/react@^11.10.4": - version "11.10.4" - resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.10.4.tgz#9dc6bccbda5d70ff68fdb204746c0e8b13a79199" - integrity sha512-j0AkMpr6BL8gldJZ6XQsQ8DnS9TxEQu1R+OGmDZiWjBAJtCcbt0tS3I/YffoqHXxH6MjgI7KdMbYKw3MEiU9eA== - dependencies: - "@babel/runtime" "^7.18.3" - "@emotion/babel-plugin" "^11.10.0" - "@emotion/cache" "^11.10.0" - "@emotion/serialize" "^1.1.0" - "@emotion/use-insertion-effect-with-fallbacks" "^1.0.0" - "@emotion/utils" "^1.2.0" - "@emotion/weak-memoize" "^0.3.0" - hoist-non-react-statics "^3.3.1" - -"@emotion/serialize@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.0.tgz#b1f97b1011b09346a40e9796c37a3397b4ea8ea8" - integrity sha512-F1ZZZW51T/fx+wKbVlwsfchr5q97iW8brAnXmsskz4d0hVB4O3M/SiA3SaeH06x02lSNzkkQv+n3AX3kCXKSFA== - dependencies: - "@emotion/hash" "^0.9.0" - "@emotion/memoize" "^0.8.0" - "@emotion/unitless" "^0.8.0" - "@emotion/utils" "^1.2.0" - csstype "^3.0.2" - -"@emotion/sheet@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.0.tgz#771b1987855839e214fc1741bde43089397f7be5" - integrity sha512-OiTkRgpxescko+M51tZsMq7Puu/KP55wMT8BgpcXVG2hqXc0Vo0mfymJ/Uj24Hp0i083ji/o0aLddh08UEjq8w== - -"@emotion/styled@^11.10.4": - version "11.10.4" - resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.10.4.tgz#e93f84a4d54003c2acbde178c3f97b421fce1cd4" - integrity sha512-pRl4R8Ez3UXvOPfc2bzIoV8u9P97UedgHS4FPX594ntwEuAMA114wlaHvOK24HB48uqfXiGlYIZYCxVJ1R1ttQ== - dependencies: - "@babel/runtime" "^7.18.3" - "@emotion/babel-plugin" "^11.10.0" - "@emotion/is-prop-valid" "^1.2.0" - "@emotion/serialize" "^1.1.0" - "@emotion/use-insertion-effect-with-fallbacks" "^1.0.0" - "@emotion/utils" "^1.2.0" - -"@emotion/unitless@^0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.0.tgz#a4a36e9cbdc6903737cd20d38033241e1b8833db" - integrity sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw== - -"@emotion/use-insertion-effect-with-fallbacks@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz#ffadaec35dbb7885bd54de3fa267ab2f860294df" - integrity sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A== - -"@emotion/utils@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.0.tgz#9716eaccbc6b5ded2ea5a90d65562609aab0f561" - integrity sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw== - -"@emotion/weak-memoize@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz#ea89004119dc42db2e1dba0f97d553f7372f6fcb" - integrity sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg== - "@eslint/eslintrc@^0.4.3": version "0.4.3" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" @@ -1646,92 +1493,6 @@ "@types/yargs" "^16.0.0" chalk "^4.0.0" -"@mui/base@5.0.0-alpha.100": - version "5.0.0-alpha.100" - resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-alpha.100.tgz#5587c98fb8c6fdf9138b92c519fe3fd79682f7ac" - integrity sha512-bSoJEKCENtmJrJDECHUe9PiqztIUACuSskyqw9ypqE7Dz3WxL3e8puFsWBkUsz+WOCjXh4B4Xljn88Ucxxv5HA== - dependencies: - "@babel/runtime" "^7.19.0" - "@emotion/is-prop-valid" "^1.2.0" - "@mui/types" "^7.2.0" - "@mui/utils" "^5.10.6" - "@popperjs/core" "^2.11.6" - clsx "^1.2.1" - prop-types "^15.8.1" - react-is "^18.2.0" - -"@mui/core-downloads-tracker@^5.10.8": - version "5.10.8" - resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.10.8.tgz#b00402dd0796e7e99a09cfc23cb0b0a6196c8e09" - integrity sha512-V5D7OInO4P9PdT/JACg7fwjbOORm3GklaMVgdGomjyxiyetgRND5CC9r35e1LK/DqHdoyDuhbFzdfrqWtpmEIw== - -"@mui/material@^5.10.8": - version "5.10.8" - resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.10.8.tgz#a223ec5f1128515107381e875f8d6257a00c3cd3" - integrity sha512-sF/Ka0IJjGXV52zoT4xAWEqXVRjNYbIjATo9L4Q5oQC5iJpGrKJFY16uNtWWB0+vp/nayAuPGZHrxtV+t3ecdQ== - dependencies: - "@babel/runtime" "^7.19.0" - "@mui/base" "5.0.0-alpha.100" - "@mui/core-downloads-tracker" "^5.10.8" - "@mui/system" "^5.10.8" - "@mui/types" "^7.2.0" - "@mui/utils" "^5.10.6" - "@types/react-transition-group" "^4.4.5" - clsx "^1.2.1" - csstype "^3.1.1" - prop-types "^15.8.1" - react-is "^18.2.0" - react-transition-group "^4.4.5" - -"@mui/private-theming@^5.10.6": - version "5.10.6" - resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.10.6.tgz#2c6bb2a4b7034cd402a099bd0349f217584e7b25" - integrity sha512-I/W0QyTLRdEx6py3lKAquKO/rNF/7j+nIOM/xCyI9kU0fcotVTcTY08mKMsS6vrzdWpi6pAkD0wP0KwWy5R5VA== - dependencies: - "@babel/runtime" "^7.19.0" - "@mui/utils" "^5.10.6" - prop-types "^15.8.1" - -"@mui/styled-engine@^5.10.8": - version "5.10.8" - resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.10.8.tgz#2db411e4278f06f70ccb6b5cd56ace67109513f6" - integrity sha512-w+y8WI18EJV6zM/q41ug19cE70JTeO6sWFsQ7tgePQFpy6ToCVPh0YLrtqxUZXSoMStW5FMw0t9fHTFAqPbngw== - dependencies: - "@babel/runtime" "^7.19.0" - "@emotion/cache" "^11.10.3" - csstype "^3.1.1" - prop-types "^15.8.1" - -"@mui/system@^5.10.8": - version "5.10.8" - resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.10.8.tgz#afea52aeed34bd2d98c993322b5b09585106953d" - integrity sha512-hRQ354zcrYP/KHqK8FheICSvE9raQaUgQaV+A3oD4JETaFUCVI9Ytt+RcQYgTqx02xlCXIjl8LK1rPjTneySqw== - dependencies: - "@babel/runtime" "^7.19.0" - "@mui/private-theming" "^5.10.6" - "@mui/styled-engine" "^5.10.8" - "@mui/types" "^7.2.0" - "@mui/utils" "^5.10.6" - clsx "^1.2.1" - csstype "^3.1.1" - prop-types "^15.8.1" - -"@mui/types@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.0.tgz#91380c2d42420f51f404120f7a9270eadd6f5c23" - integrity sha512-lGXtFKe5lp3UxTBGqKI1l7G8sE2xBik8qCfrLHD5olwP/YU0/ReWoWT7Lp1//ri32dK39oPMrJN8TgbkCSbsNA== - -"@mui/utils@^5.10.6": - version "5.10.6" - resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.10.6.tgz#98d432d2b05544c46efe356cf095cea3a37c2e59" - integrity sha512-g0Qs8xN/MW2M3fLL8197h5J2VB9U+49fLlnKKqC6zy/yus5cZwdT+Gwec+wUMxgwQoxMDn+J8oDWAn28kEOR/Q== - dependencies: - "@babel/runtime" "^7.19.0" - "@types/prop-types" "^15.7.5" - "@types/react-is" "^16.7.1 || ^17.0.0" - prop-types "^15.8.1" - react-is "^18.2.0" - "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -1781,11 +1542,6 @@ schema-utils "^2.6.5" source-map "^0.7.3" -"@popperjs/core@^2.11.6": - version "2.11.6" - resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45" - integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw== - "@popperjs/core@^2.9.2": version "2.10.2" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.10.2.tgz#0798c03351f0dea1a5a4cabddf26a55a7cbee590" @@ -2205,11 +1961,6 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== -"@types/prop-types@^15.7.5": - version "15.7.5" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" - integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== - "@types/q@^1.5.1": version "1.5.5" resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.5.tgz#75a2a8e7d8ab4b230414505d92335d1dcb53a6df" @@ -2246,13 +1997,6 @@ dependencies: "@types/react" "*" -"@types/react-is@^16.7.1 || ^17.0.0": - version "17.0.3" - resolved "https://registry.yarnpkg.com/@types/react-is/-/react-is-17.0.3.tgz#2d855ba575f2fc8d17ef9861f084acc4b90a137a" - integrity sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw== - dependencies: - "@types/react" "*" - "@types/react-router-dom@^5.3.1": version "5.3.1" resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.1.tgz#76700ccce6529413ec723024b71f01fc77a4a980" @@ -2270,13 +2014,6 @@ "@types/history" "*" "@types/react" "*" -"@types/react-transition-group@^4.4.5": - version "4.4.5" - resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.5.tgz#aae20dcf773c5aa275d5b9f7cdbca638abc5e416" - integrity sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA== - dependencies: - "@types/react" "*" - "@types/react@*", "@types/react@^17.0.0": version "17.0.30" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.30.tgz#2f8e6f5ab6415c091cc5e571942ee9064b17609e" @@ -3186,15 +2923,6 @@ babel-plugin-macros@2.8.0: cosmiconfig "^6.0.0" resolve "^1.12.0" -babel-plugin-macros@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" - integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== - dependencies: - "@babel/runtime" "^7.12.5" - cosmiconfig "^7.0.0" - resolve "^1.19.0" - babel-plugin-named-asset-import@^0.3.7: version "0.3.7" resolved "https://registry.yarnpkg.com/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.7.tgz#156cd55d3f1228a5765774340937afc8398067dd" @@ -3903,11 +3631,6 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" -clsx@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" - integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== - co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -4123,11 +3846,6 @@ convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: dependencies: safe-buffer "~5.1.1" -convert-source-map@^1.5.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" - integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== - cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -4546,11 +4264,6 @@ csstype@^3.0.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.9.tgz#6410af31b26bd0520933d02cbc64fce9ce3fbf0b" integrity sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw== -csstype@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9" - integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== - cyclist@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" @@ -4833,14 +4546,6 @@ dom-converter@^0.2.0: dependencies: utila "~0.4" -dom-helpers@^5.0.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" - integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== - dependencies: - "@babel/runtime" "^7.8.7" - csstype "^3.0.2" - dom-serializer@0: version "0.2.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" @@ -5779,11 +5484,6 @@ find-cache-dir@^3.3.1: make-dir "^3.0.2" pkg-dir "^4.1.0" -find-root@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" - integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== - find-up@4.1.0, find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -6275,7 +5975,7 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.1: +hoist-non-react-statics@^3.1.0: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -6763,13 +6463,6 @@ is-core-module@^2.0.0, is-core-module@^2.2.0, is-core-module@^2.7.0: dependencies: has "^1.0.3" -is-core-module@^2.9.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed" - integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== - dependencies: - has "^1.0.3" - is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -8911,7 +8604,7 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-parse@^1.0.6, path-parse@^1.0.7: +path-parse@^1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== @@ -9878,15 +9571,6 @@ prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2: object-assign "^4.1.1" react-is "^16.8.1" -prop-types@^15.8.1: - version "15.8.1" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" - integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== - dependencies: - loose-envify "^1.4.0" - object-assign "^4.1.1" - react-is "^16.13.1" - proxy-addr@~2.0.5: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -10143,7 +9827,7 @@ react-fast-compare@^3.0.1: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== -react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: +react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -10153,11 +9837,6 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^18.2.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" - integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== - react-onclickoutside@^6.12.0: version "6.12.1" resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.12.1.tgz#92dddd28f55e483a1838c5c2930e051168c1e96b" @@ -10271,16 +9950,6 @@ react-scripts@4.0.3: optionalDependencies: fsevents "^2.1.3" -react-transition-group@^4.4.5: - version "4.4.5" - resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" - integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== - dependencies: - "@babel/runtime" "^7.5.5" - dom-helpers "^5.0.1" - loose-envify "^1.4.0" - prop-types "^15.6.2" - react@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" @@ -10570,15 +10239,6 @@ resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.1 is-core-module "^2.2.0" path-parse "^1.0.6" -resolve@^1.19.0: - version "1.22.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" - integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== - dependencies: - is-core-module "^2.9.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - resolve@^2.0.0-next.3: version "2.0.0-next.3" resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.3.tgz#d41016293d4a8586a39ca5d9b5f15cbea1f55e46" @@ -11162,7 +10822,7 @@ source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, sourc resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7: +source-map@^0.5.0, source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== @@ -11493,11 +11153,6 @@ stylehacks@^4.0.0: postcss "^7.0.0" postcss-selector-parser "^3.0.0" -stylis@4.0.13: - version "4.0.13" - resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91" - integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag== - supports-color@8.1.1: version "8.1.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" @@ -11534,11 +11189,6 @@ supports-hyperlinks@^2.0.0: has-flag "^4.0.0" supports-color "^7.0.0" -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - svg-parser@^2.0.2: version "2.0.4" resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" From 073b4a9bd2f7887b5e3e89b9408ac72deeefcb9a Mon Sep 17 00:00:00 2001 From: Ido Date: Wed, 12 Oct 2022 17:42:03 -0400 Subject: [PATCH 12/23] Removed MUI imports --- client/src/pages/VotePage.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/src/pages/VotePage.tsx b/client/src/pages/VotePage.tsx index f9fded7..4a6b597 100644 --- a/client/src/pages/VotePage.tsx +++ b/client/src/pages/VotePage.tsx @@ -9,9 +9,6 @@ import { questionStarted, } from "../constants/constants"; import { socket } from "../socket"; -import Alert from "@mui/material/Alert"; -import Snackbar from "@mui/material/Snackbar"; -import CircularProgress from "@mui/material/CircularProgress"; export const VotePage = () => { const history = useHistory(); From 7cabb3a74b5157839239f2503b6eccd22263b671 Mon Sep 17 00:00:00 2001 From: Ido Date: Sat, 22 Oct 2022 15:46:33 -0400 Subject: [PATCH 13/23] Uses tailwind's error dialog instead of javascripts's alert --- client/src/components/ErrorAlert.tsx | 93 ++++++++++++++++++++++++++++ client/src/pages/VotePage.tsx | 18 ++++-- 2 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 client/src/components/ErrorAlert.tsx diff --git a/client/src/components/ErrorAlert.tsx b/client/src/components/ErrorAlert.tsx new file mode 100644 index 0000000..b116912 --- /dev/null +++ b/client/src/components/ErrorAlert.tsx @@ -0,0 +1,93 @@ +import React from "react"; + +interface ErrorAlertProps { + title: string; + text?: string; + onClose: () => void; + enabled: boolean; + buttonText?: string; +} + +export const ErrorAlert = ({ + title, + text, + onClose, + enabled, + buttonText, +}: ErrorAlertProps) => { + return ( +
+ {/* Background backdrop, show/hide based on modal state. */} + + {/* Entering: "ease-out duration-300" */} + {/* From: "opacity-0" */} + {/* To: "opacity-100" */} + {/* Leaving: "ease-in duration-200" */} + {/* From: "opacity-100" */} + {/* To: "opacity-0" */} +
+ +
+
+ {/* Modal panel, show/hide based on modal state. */} + + {/* Entering: "ease-out duration-300" */} + {/* From: "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" */} + {/* To: "opacity-100 translate-y-0 sm:scale-100" */} + {/* Leaving: "ease-in duration-200" */} + {/* From: "opacity-100 translate-y-0 sm:scale-100" */} + {/* To: "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" */} +
+
+
+
+ {/* Heroicon name: outline/exclamation-triangle */} + +
+
+ +
+

{text}

+
+
+
+
+
+ +
+
+
+
+
+ ); +}; diff --git a/client/src/pages/VotePage.tsx b/client/src/pages/VotePage.tsx index 4a6b597..ebe97d3 100644 --- a/client/src/pages/VotePage.tsx +++ b/client/src/pages/VotePage.tsx @@ -9,6 +9,7 @@ import { questionStarted, } from "../constants/constants"; import { socket } from "../socket"; +import { ErrorAlert } from "../components/ErrorAlert"; export const VotePage = () => { const history = useHistory(); @@ -17,7 +18,8 @@ export const VotePage = () => { const [started, setStarted] = useState(false); const [loading, setLoading] = useState(false); - const [timeOutCode, setTimeOutCode] = useState(0); + const [timeoutCode, setTimeoutCode] = useState(0); + const [timeoutError, setTimeoutError] = useState(false); const [errorCode, setErrorCode] = useState(0); const [selectedOption, setSelectionOption] = useState(""); @@ -76,7 +78,7 @@ export const VotePage = () => { const voteAckHandler = (data: any) => { setSelectionOption(String.fromCharCode(data + 64)); - clearTimeout(timeOutCode); + clearTimeout(timeoutCode); setLoading(false); }; socket.on("ack", voteAckHandler); @@ -91,7 +93,7 @@ export const VotePage = () => { const pollButtonHandler = (selectedOption: string) => { setLoading(true); - setTimeOutCode( + setTimeoutCode( setTimeout(() => { triggerTimeOutError(); setLoading(false); @@ -100,7 +102,7 @@ export const VotePage = () => { socket.emit("vote", (selectedOption.charCodeAt(0) % 65) + 1); }; const triggerTimeOutError = () => { - alert("Vote not received, please try again!"); + setTimeoutError(true); }; const optionButtons = () => { const pollOptionButtons = []; @@ -158,6 +160,14 @@ export const VotePage = () => { ) : null} + { + setTimeoutError(false); + }} + /> ); }; From 19bfdcafb9a9472883ccd5dbb3410e4002cf69a7 Mon Sep 17 00:00:00 2001 From: Ido Date: Sun, 23 Oct 2022 19:14:20 -0400 Subject: [PATCH 14/23] Centered spinner Thanks @embeddedt Co-authored-by: embeddedt <42941056+embeddedt@users.noreply.github.com> --- client/src/pages/VotePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/pages/VotePage.tsx b/client/src/pages/VotePage.tsx index ebe97d3..768cc53 100644 --- a/client/src/pages/VotePage.tsx +++ b/client/src/pages/VotePage.tsx @@ -140,7 +140,7 @@ export const VotePage = () => {
- - Loading... -
+ + Loading...
) : null} From ecb4c4ff2353ad6737bca497a7cdc546ff9a71f2 Mon Sep 17 00:00:00 2001 From: Ido Date: Mon, 24 Oct 2022 23:43:41 -0400 Subject: [PATCH 16/23] Linting and Formatting for the backend (#71) * eslint setup (missing husky integration) #62 * Fixed eslint errors * Added lint:fix script to package.json * added server pre-commit husky Co-authored-by: Shubh Bapna --- .github/workflows/ci.yml | 5 + .husky/pre-commit | 3 + server/.eslintignore | 3 + server/.eslintrc.json | 25 ++++ server/package.json | 5 +- server/src/controllers/pollController.ts | 164 ++++++++++----------- server/src/controllers/socketController.ts | 102 ++++++------- server/src/controllers/userController.ts | 15 +- server/src/db/mogoose.ts | 16 +- server/src/db/schema.ts | 22 +-- server/src/redis.ts | 30 ++-- server/src/routes/pollRoute.ts | 102 ++++++------- server/src/routes/userRoutes.ts | 20 +-- server/src/server.ts | 82 +++++------ server/src/socket.ts | 44 +++--- server/yarn.lock | 5 + 16 files changed, 340 insertions(+), 303 deletions(-) create mode 100644 server/.eslintignore create mode 100644 server/.eslintrc.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 460ae61..6e3dc27 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,3 +13,8 @@ jobs: steps: - uses: actions/checkout@v2 - run: cd client && yarn --frozen-lockfile && yarn lint + lint_server: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: cd server && yarn --frozen-lockfile && yarn lint diff --git a/.husky/pre-commit b/.husky/pre-commit index ea2e0e0..2a952ae 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -3,3 +3,6 @@ cd client npx lint-staged + +cd ../server +yarn lint \ No newline at end of file diff --git a/server/.eslintignore b/server/.eslintignore new file mode 100644 index 0000000..3881e74 --- /dev/null +++ b/server/.eslintignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +.idea diff --git a/server/.eslintrc.json b/server/.eslintrc.json new file mode 100644 index 0000000..09213d7 --- /dev/null +++ b/server/.eslintrc.json @@ -0,0 +1,25 @@ +{ + "env": { + "node": true + }, + "extends": [ + "standard", + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended"], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 13, + "sourceType": "module" + }, + "plugins": ["@typescript-eslint"], + "rules": { + "no-use-before-define": "off", + "no-unused-vars": "warn", + "no-console": "off", + "camelcase": "off", + "no-throw-literal": "warn" + }, + "settings": { + } +} diff --git a/server/package.json b/server/package.json index 33fdf3c..273b04f 100644 --- a/server/package.json +++ b/server/package.json @@ -8,7 +8,9 @@ "prestart": "tsc", "start": "node .", "dev": "nodemon --watch src -e ts,ejs --exec yarn run start", - "build": "tsc --project ./" + "build": "tsc --project ./", + "lint": "eslint --ext .ts .", + "lint:fix": "eslint --ext .ts --fix ." }, "repository": "git+https://github.com/hiimchrislim/QuizVotingSystem.git", "author": "Chris, Akshit, Shubh, Ilir", @@ -42,6 +44,7 @@ "eslint-plugin-import": "^2.25.2", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^5.1.1", + "husky": "^8.0.1", "nodemon": "^2.0.14", "prettier": "^2.4.1", "socket.io-client": "^4.3.2", diff --git a/server/src/controllers/pollController.ts b/server/src/controllers/pollController.ts index ad06951..51abe7f 100644 --- a/server/src/controllers/pollController.ts +++ b/server/src/controllers/pollController.ts @@ -1,76 +1,77 @@ -import { Poll } from "../db/schema"; -import { PollModel, StudentModel } from "../db/mogoose"; -import { io } from "../socket"; -import { client } from "../redis"; -import { customAlphabet } from "nanoid/async"; -import { pollResult } from "./socketController"; -const nanoid = customAlphabet("qwertyuiopasdfghjklzxcvbnm1234567890", 6); +import { Poll } from '../db/schema' +import { PollModel, StudentModel } from '../db/mogoose' +import { io } from '../socket' +import { client } from '../redis' +import { customAlphabet } from 'nanoid/async' +import { pollResult } from './socketController' +const nanoid = customAlphabet('qwertyuiopasdfghjklzxcvbnm1234567890', 6) // set poll code expiry to 1 day -const expiry = 60 * 60 * 24; +const expiry = 60 * 60 * 24 -async function createPoll(poll: Poll) { - if (!poll.hasOwnProperty("courseCode")) - return { status: 400, data: { message: "courseCode is required" } }; - const promise = [nanoid(), PollModel.create(poll)] as const; - const result = await Promise.all(promise); - const pollId = result[1]["_id"].toString(); - const pollCode = result[0]; - await client.set(pollCode, pollId, { EX: expiry, NX: true }); +async function createPoll (poll: Poll) { + if (!poll.courseCode) { return { status: 400, data: { message: 'courseCode is required' } } } + const promise = [nanoid(), PollModel.create(poll)] as const + const result = await Promise.all(promise) + const pollId = result[1]._id.toString() + const pollCode = result[0] + await client.set(pollCode, pollId, { EX: expiry, NX: true }) - return { status: 201, data: { pollCode, pollId } }; + return { status: 201, data: { pollCode, pollId } } } -async function changePollStatus(pollId: string, hasStarted: boolean) { - if (typeof hasStarted !== "boolean") +async function changePollStatus (pollId: string, hasStarted: boolean) { + if (typeof hasStarted !== 'boolean') { return { status: 400, - data: { message: "hasStarted should be boolean" }, - }; - const currSequence = await client.get(pollId); - console.log("currSequence", currSequence); - let newSequence; + data: { message: 'hasStarted should be boolean' } + } + } + const currSequence = await client.get(pollId) + console.log('currSequence', currSequence) + let newSequence // on every new start increment the sequence counter if (hasStarted) { - if (currSequence == null) newSequence = 0; - else newSequence = parseInt(currSequence); - if (newSequence < 0) newSequence *= -1; - newSequence++; - console.log("newSequence", newSequence); + if (currSequence == null) newSequence = 0 + else newSequence = parseInt(currSequence) + if (newSequence < 0) newSequence *= -1 + newSequence++ + console.log('newSequence', newSequence) await client.set(pollId, newSequence.toString(), { - EX: expiry, - }); - const result = await pollResult(pollId, newSequence); - io.to(pollId).emit("result", result); - } - // for every stop make the current counter negative to indicate that it is not an active sequence - else { + EX: expiry + }) + const result = await pollResult(pollId, newSequence) + io.to(pollId).emit('result', result) + } else { + // for every stop make the current counter negative to indicate that it is not an active sequence if (currSequence != null) { - newSequence = parseInt(currSequence) * -1; + newSequence = parseInt(currSequence) * -1 await client.set(pollId, newSequence.toString(), { - EX: expiry, - }); + EX: expiry + }) } } - io.to(pollId).emit("pollStarted", hasStarted); + io.to(pollId).emit('pollStarted', hasStarted) - return { status: 200, data: { message: "poll status successfully changed" } }; + return { status: 200, data: { message: 'poll status successfully changed' } } } -async function getStudents(courseCode: string, startTime: Date, endTime: Date) { +async function getStudents (courseCode: string, startTime: Date, endTime: Date) { + // TODO Already addressed in TODO bellow + // eslint-disable-next-line no-useless-catch try { - const pollDoc = await PollModel.find({ courseCode }); - let promises: Promise[] = []; - let responses: any[] = []; + const pollDoc = await PollModel.find({ courseCode }) + const promises: Promise[] = [] + const responses: any[] = [] pollDoc.forEach((element) => { promises.push( StudentModel.aggregate([ { $match: { pollId: element._id.toString(), - timestamp: { $gte: startTime, $lte: endTime }, - }, + timestamp: { $gte: startTime, $lte: endTime } + } }, { $project: { @@ -81,57 +82,54 @@ async function getStudents(courseCode: string, startTime: Date, endTime: Date) { utorid: 1, timestamp: { $dateToString: { - date: "$timestamp", - timezone: "America/Toronto" - }, + date: '$timestamp', + timezone: 'America/Toronto' + } }, pollName: element.name, description: element.description, - answer: 1, - }, - }, + answer: 1 + } + } ]).then((data) => { data.forEach((val) => { - responses.push(val); - }); + responses.push(val) + }) }) - ); - }); - await Promise.all(promises); - console.log(responses); - return { responses }; + ) + }) + await Promise.all(promises) + console.log(responses) + return { responses } } catch (err) { /** * TODO: Add error handler */ - throw err; + throw err } } -async function getPollStatus(pollId: any) { - if (pollId === null || pollId === undefined || typeof pollId !== "string") - return { status: 400, data: { message: "Invalid poll Id" } }; - const result = await client.get(pollId); - const pollStarted = result === null ? false : parseInt(result) > 0; - return { status: 200, data: { pollStarted } }; +async function getPollStatus (pollId: any) { + if (pollId === null || pollId === undefined || typeof pollId !== 'string') { return { status: 400, data: { message: 'Invalid poll Id' } } } + const result = await client.get(pollId) + const pollStarted = result === null ? false : parseInt(result) > 0 + return { status: 200, data: { pollStarted } } } -async function getResult(pollId: any) { - if (pollId === null || pollId === undefined || typeof pollId !== "string") - return { status: 400, data: { message: "Invalid poll Id" } }; - const currSequence = await client.get(pollId); - const result = await pollResult(pollId, parseInt(currSequence)); - return { status: 200, data: { ...result } }; +async function getResult (pollId: any) { + if (pollId === null || pollId === undefined || typeof pollId !== 'string') { return { status: 400, data: { message: 'Invalid poll Id' } } } + const currSequence = await client.get(pollId) + const result = await pollResult(pollId, parseInt(currSequence)) + return { status: 200, data: { ...result } } } -async function endForever(pollCode: string) { - if (pollCode === null || pollCode === undefined) - return { status: 400, data: { message: "Invalid poll code" } }; - const pollId = await client.get(pollCode); - await Promise.all([client.del(pollCode), client.del(pollId)]); - io.to(pollId).emit("end", true); - io.of("/").in(pollId).disconnectSockets(); - return { status: 200, data: { message: "Poll closed" } }; +async function endForever (pollCode: string) { + if (pollCode === null || pollCode === undefined) { return { status: 400, data: { message: 'Invalid poll code' } } } + const pollId = await client.get(pollCode) + await Promise.all([client.del(pollCode), client.del(pollId)]) + io.to(pollId).emit('end', true) + io.of('/').in(pollId).disconnectSockets() + return { status: 200, data: { message: 'Poll closed' } } } export { @@ -140,5 +138,5 @@ export { getStudents, getPollStatus, getResult, - endForever, -}; + endForever +} diff --git a/server/src/controllers/socketController.ts b/server/src/controllers/socketController.ts index ad7b023..71b9a39 100644 --- a/server/src/controllers/socketController.ts +++ b/server/src/controllers/socketController.ts @@ -1,75 +1,71 @@ -import { StudentModel } from "../db/mogoose"; -import { io } from "../socket"; -import { Socket } from "socket.io"; -import { client } from "../redis"; +import { StudentModel } from '../db/mogoose' +import { io } from '../socket' +import { Socket } from 'socket.io' +import { client } from '../redis' -async function join(socket: Socket, pollCode: string) { +async function join (socket: Socket, pollCode: string) { try { - console.log(`join: ${socket.id}`); - if (pollCode === null || pollCode === undefined) - throw { code: 1, message: "Invalid poll code" }; - const pollId = await client.get(pollCode); - console.log(pollId); - if (pollId === null) throw { code: 1, message: "Invalid poll code" }; + console.log(`join: ${socket.id}`) + if (pollCode === null || pollCode === undefined) { throw { code: 1, message: 'Invalid poll code' } } + const pollId = await client.get(pollCode) + console.log(pollId) + if (pollId === null) throw { code: 1, message: 'Invalid poll code' } // ensure that socket is connected to 1 room (other than the default room) socket.rooms.forEach((room) => { - if (room !== socket.id) socket.leave(room); - }); + if (room !== socket.id) socket.leave(room) + }) - const currSequence = await client.get(pollId); + const currSequence = await client.get(pollId) const hasStarted = - currSequence == null ? false : parseInt(currSequence) > 0; - console.log("Has Started", hasStarted); - socket.join(pollId); - socket.data["pollId"] = pollId; - io.to(socket.id).emit("pollStarted", hasStarted); + currSequence == null ? false : parseInt(currSequence) > 0 + console.log('Has Started', hasStarted) + socket.join(pollId) + socket.data.pollId = pollId + io.to(socket.id).emit('pollStarted', hasStarted) } catch (err) { - console.log(err); - io.to(socket.id).emit("error", err); + console.log(err) + io.to(socket.id).emit('error', err) } } -async function pollResult(pollId: string, sequence: number) { +async function pollResult (pollId: string, sequence: number) { try { const result = await StudentModel.aggregate([ { $match: { pollId, sequence } }, { $facet: { - result: [{ $group: { _id: "$answer", count: { $sum: 1 } } }], - totalVotes: [{ $count: "totalVotes" }], - }, - }, + result: [{ $group: { _id: '$answer', count: { $sum: 1 } } }], + totalVotes: [{ $count: 'totalVotes' }] + } + } ]).then((data: any): any => { return { result: data[0].result, totalVotes: - data[0].totalVotes.length > 0 ? data[0].totalVotes[0].totalVotes : 0, - }; - }); - console.log(result); - return result; + data[0].totalVotes.length > 0 ? data[0].totalVotes[0].totalVotes : 0 + } + }) + console.log(result) + return result } catch (err) { - console.log(err); + console.log(err) } } -async function vote(socket: Socket, answer: number, utorid: string) { +async function vote (socket: Socket, answer: number, utorid: string) { try { - console.log(`vote: ${socket.id}`); - let pollId = socket.data.pollId; - if (pollId === null || pollId === undefined) - throw { code: 1, message: "haven't joined any room" }; - const currSequence = await client.get(pollId); - console.log(currSequence); + console.log(`vote: ${socket.id}`) + const pollId = socket.data.pollId + if (pollId === null || pollId === undefined) { throw { code: 1, message: "haven't joined any room" } } + const currSequence = await client.get(pollId) + console.log(currSequence) if (currSequence === null || parseInt(currSequence) < 0) { - throw { code: 2, message: "Poll not live yet" }; + throw { code: 2, message: 'Poll not live yet' } } - if (utorid === undefined || utorid === null) - throw { code: 1, message: "Invalid utorid" }; - if (answer === undefined || answer === null) - throw { code: 2, message: "Invalid answer" }; + if (utorid === undefined || utorid === null) { throw { code: 1, message: 'Invalid utorid' } } + if (answer === undefined || answer === null) { throw { code: 2, message: 'Invalid answer' } } await StudentModel.updateOne( { @@ -81,19 +77,19 @@ async function vote(socket: Socket, answer: number, utorid: string) { utorid, sequence: parseInt(currSequence), answer, - timestamp: new Date(), + timestamp: new Date() }, { upsert: true } - ); + ) pollResult(pollId, parseInt(currSequence)).then((data) => { - io.to(pollId).emit("result", data); - }); - io.to(socket.id).emit("ack", answer); - return; + io.to(pollId).emit('result', data) + }) + io.to(socket.id).emit('ack', answer) + return } catch (err) { - console.log(err); - io.to(socket.id).emit("error", err); + console.log(err) + io.to(socket.id).emit('error', err) } } -export { vote, join, pollResult }; +export { vote, join, pollResult } diff --git a/server/src/controllers/userController.ts b/server/src/controllers/userController.ts index 82d4388..02d258d 100644 --- a/server/src/controllers/userController.ts +++ b/server/src/controllers/userController.ts @@ -1,11 +1,10 @@ -import { client } from "../redis"; +import { client } from '../redis' -async function getUser(utorid: any) { - if (utorid === null || utorid === undefined || typeof utorid !== "string") - return { status: 400, data: { message: "Invalid utorid" } }; - let userType = await client.get(utorid); - if (userType == null) userType = "student"; - return { status: 200, data: { userType } }; +async function getUser (utorid: any) { + if (utorid === null || utorid === undefined || typeof utorid !== 'string') { return { status: 400, data: { message: 'Invalid utorid' } } } + let userType = await client.get(utorid) + if (userType == null) userType = 'student' + return { status: 200, data: { userType } } } -export { getUser }; +export { getUser } diff --git a/server/src/db/mogoose.ts b/server/src/db/mogoose.ts index ead7cf7..10ca3e2 100644 --- a/server/src/db/mogoose.ts +++ b/server/src/db/mogoose.ts @@ -1,16 +1,16 @@ -import { model, connect, connection } from "mongoose"; +import { model, connect, connection } from 'mongoose' import { PollDocument, pollSchema, StudentDocument, - studentSchema, -} from "./schema"; + studentSchema +} from './schema' -export const PollModel = model("Poll", pollSchema); -export const StudentModel = model("Student", studentSchema); +export const PollModel = model('Poll', pollSchema) +export const StudentModel = model('Student', studentSchema) connect(process.env.MONGODB_URL).catch((err) => { - throw err; -}); + throw err +}) -export const db = connection; +export const db = connection diff --git a/server/src/db/schema.ts b/server/src/db/schema.ts index 4f36ea4..2a9d99f 100644 --- a/server/src/db/schema.ts +++ b/server/src/db/schema.ts @@ -1,4 +1,4 @@ -import { Schema, Document, Types } from "mongoose"; +import { Schema, Document, Types } from 'mongoose' /** * student subdocument. Used as nested objects in PollResults schema @@ -18,25 +18,25 @@ export interface StudentDocument extends Student, Document {} export const studentSchema = new Schema({ utorid: { type: String, - required: true, + required: true }, answer: { type: Number, - required: true, + required: true }, timestamp: { type: Date, - required: true, + required: true }, sequence: { type: Number, - required: true, + required: true }, pollId: { type: String, - required: true, - }, -}); + required: true + } +}) /** * Poll schema. Represents how a single poll will look like @@ -64,7 +64,7 @@ export const pollSchema = new Schema({ description: String, courseCode: { type: String, required: true }, created: { type: Date, required: true }, - options: Number, -}); + options: Number +}) -export const ObjectId = Types.ObjectId; +export const ObjectId = Types.ObjectId diff --git a/server/src/redis.ts b/server/src/redis.ts index 3bf6d41..9a39ad8 100644 --- a/server/src/redis.ts +++ b/server/src/redis.ts @@ -1,30 +1,30 @@ -import { createClient } from "redis"; -import fs from "fs"; -import path from "path"; -import readline from "readline"; +import { createClient } from 'redis' +import fs from 'fs' +import path from 'path' +import readline from 'readline' const client = createClient({ - url: process.env.REDIS_URL, + url: process.env.REDIS_URL }); (async () => { - client.on("error", (err) => console.log("Redis Client Error", err)); + client.on('error', (err) => console.log('Redis Client Error', err)) - await client.connect(); - console.log("connected to redis"); + await client.connect() + console.log('connected to redis') const readstream = fs.createReadStream( path.join(__dirname, process.env.WHITELIST) - ); + ) const rl = readline.createInterface({ input: readstream, - crlfDelay: Infinity, - }); + crlfDelay: Infinity + }) for await (const line of rl) { - console.log(line.trim()); - client.set(line.trim(), "instructor"); + console.log(line.trim()) + client.set(line.trim(), 'instructor') } -})(); +})() -export { client }; +export { client } diff --git a/server/src/routes/pollRoute.ts b/server/src/routes/pollRoute.ts index 7a58775..450a34d 100644 --- a/server/src/routes/pollRoute.ts +++ b/server/src/routes/pollRoute.ts @@ -1,92 +1,92 @@ -import { Router } from "express"; +import { Router } from 'express' import { changePollStatus, createPoll, getPollStatus, getResult, getStudents, - endForever, -} from "../controllers/pollController"; + endForever +} from '../controllers/pollController' -const pollRouter = Router(); +const pollRouter = Router() -pollRouter.post("/", async (req, res) => { - const { name, description, courseCode, options } = req.body; +pollRouter.post('/', async (req, res) => { + const { name, description, courseCode, options } = req.body try { const poll = { name, description, courseCode: courseCode.toUpperCase(), options, - created: new Date(), - }; - const result = await createPoll(poll); - return res.status(result.status).send(result.data); + created: new Date() + } + const result = await createPoll(poll) + return res.status(result.status).send(result.data) } catch (err) { - console.log(err); - return res.status(500).send({ message: "Internal Server Error" }); + console.log(err) + return res.status(500).send({ message: 'Internal Server Error' }) } -}); +}) -pollRouter.patch("/:pollId", async (req, res) => { - const { pollId } = req.params; - const { hasStarted } = req.body; +pollRouter.patch('/:pollId', async (req, res) => { + const { pollId } = req.params + const { hasStarted } = req.body try { - const result = await changePollStatus(pollId, hasStarted); - return res.status(result.status).send(result.data); + const result = await changePollStatus(pollId, hasStarted) + return res.status(result.status).send(result.data) } catch (err) { - console.log(err); - return res.status(500).send({ message: "Internal Server Error" }); + console.log(err) + return res.status(500).send({ message: 'Internal Server Error' }) } -}); +}) -pollRouter.get("/students", async (req, res) => { - const courseCode = req.query.courseCode as string; - const startTime = req.query.startTime as string; - const endTime = req.query.endTime as string; - //const { startTime, endTime } = req.body; - console.log(req.query); - console.log(startTime, endTime); +pollRouter.get('/students', async (req, res) => { + const courseCode = req.query.courseCode as string + const startTime = req.query.startTime as string + const endTime = req.query.endTime as string + // const { startTime, endTime } = req.body; + console.log(req.query) + console.log(startTime, endTime) try { const result = await getStudents( courseCode.toUpperCase(), new Date(startTime), new Date(endTime) - ); - return res.status(200).send(result); + ) + return res.status(200).send(result) } catch (err) { - return res.status(500).send({ message: "Failed to find students" }); + return res.status(500).send({ message: 'Failed to find students' }) } -}); +}) -pollRouter.get("/status", async (req, res) => { - const { pollId } = req.query; +pollRouter.get('/status', async (req, res) => { + const { pollId } = req.query try { - const result = await getPollStatus(pollId); - return res.status(result.status).send(result.data); + const result = await getPollStatus(pollId) + return res.status(result.status).send(result.data) } catch (err) { - return res.status(500).send({ message: "Internal Server Error" }); + return res.status(500).send({ message: 'Internal Server Error' }) } -}); +}) -pollRouter.get("/result", async (req, res) => { - const { pollId } = req.query; +pollRouter.get('/result', async (req, res) => { + const { pollId } = req.query try { - const result = await getResult(pollId); - return res.status(result.status).send(result.data); + const result = await getResult(pollId) + return res.status(result.status).send(result.data) } catch (err) { - return res.status(500).send({ message: "Internal Server Error" }); + return res.status(500).send({ message: 'Internal Server Error' }) } -}); +}) -pollRouter.patch("/end/:pollCode", async (req, res) => { - const { pollCode } = req.params; +pollRouter.patch('/end/:pollCode', async (req, res) => { + const { pollCode } = req.params try { - const result = await endForever(pollCode); - return res.status(result.status).send(result.data); + const result = await endForever(pollCode) + return res.status(result.status).send(result.data) } catch (err) { - return res.status(500).send({ message: "Internal Server Error" }); + return res.status(500).send({ message: 'Internal Server Error' }) } -}); +}) -export default pollRouter; +export default pollRouter diff --git a/server/src/routes/userRoutes.ts b/server/src/routes/userRoutes.ts index 0057905..cb5d0a9 100644 --- a/server/src/routes/userRoutes.ts +++ b/server/src/routes/userRoutes.ts @@ -1,16 +1,16 @@ -import { Router } from "express"; -import { getUser } from "../controllers/userController"; +import { Router } from 'express' +import { getUser } from '../controllers/userController' -const userRouter = Router(); +const userRouter = Router() -userRouter.get("/", async (req, res) => { +userRouter.get('/', async (req, res) => { try { - const result = await getUser(req.headers.utorid); - return res.status(result.status).send(result.data); + const result = await getUser(req.headers.utorid) + return res.status(result.status).send(result.data) } catch (err) { - console.log(err); - return res.status(500).send({ message: "Internal Server Error" }); + console.log(err) + return res.status(500).send({ message: 'Internal Server Error' }) } -}); +}) -export default userRouter; +export default userRouter diff --git a/server/src/server.ts b/server/src/server.ts index 5314d71..4050c02 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -1,62 +1,62 @@ -"use strict"; +'use strict' -import * as dotenv from "dotenv"; -dotenv.config(); -import express from "express"; -import cors from "cors"; -import cookieParser from "cookie-parser"; -import { io } from "./socket"; -import pollRouter from "./routes/pollRoute"; -import { db } from "./db/mogoose"; -import userRouter from "./routes/userRoutes"; -import { getUser } from "./controllers/userController"; -db.on("open", () => { - console.log("Connected to mongo"); -}); +import * as dotenv from 'dotenv' +import express from 'express' +import cors from 'cors' +import cookieParser from 'cookie-parser' +import { io } from './socket' +import pollRouter from './routes/pollRoute' +import { db } from './db/mogoose' +import userRouter from './routes/userRoutes' +import { getUser } from './controllers/userController' +dotenv.config() +db.on('open', () => { + console.log('Connected to mongo') +}) // starting the express server -const app = express(); -const port = process.env.PORT || 5000; +const app = express() +const port = process.env.PORT || 5000 // parse cookies and body and enable cors app.use( cors({ origin: process.env.FRONTEND_URL, credentials: true, - methods: "GET,POST,DELETE,PATCH", + methods: 'GET,POST,DELETE,PATCH' }) -); -app.use(express.urlencoded({ extended: true })); -app.use(express.json()); -app.use(cookieParser()); -app.use("/user", userRouter); +) +app.use(express.urlencoded({ extended: true })) +app.use(express.json()) +app.use(cookieParser()) +app.use('/user', userRouter) app.use(async (req, res, next) => { try { - const userType = await getUser(req.headers.utorid); - if (userType.data.userType === "instructor") next(); - else next(new Error("Forbidden User")); + const userType = await getUser(req.headers.utorid) + if (userType.data.userType === 'instructor') next() + else next(new Error('Forbidden User')) } catch (err) { - next(new Error("Forbidden User")); + next(new Error('Forbidden User')) } -}); -app.use("/poll", pollRouter); +}) +app.use('/poll', pollRouter) const server = app.listen(port, () => { - console.log("Listening on http://localhost:" + port); - io.attach(server); + console.log('Listening on http://localhost:' + port) + io.attach(server) io.use(async (socket, next) => { try { - if (socket.handshake.headers.utorid != undefined) { - socket.data["utorid"] = socket.handshake.headers.utorid; - next(); - return; - } else if (socket.data.utorid != undefined) { - next(); - return; + if (socket.handshake.headers.utorid) { + socket.data.utorid = socket.handshake.headers.utorid + next() + return + } else if (socket.data.utorid) { + next() + return } - next(new Error("Not Authorized")); + next(new Error('Not Authorized')) } catch (err) { - next(new Error("Not Authorized")); + next(new Error('Not Authorized')) } - }); -}); + }) +}) diff --git a/server/src/socket.ts b/server/src/socket.ts index 09dac3a..781e940 100644 --- a/server/src/socket.ts +++ b/server/src/socket.ts @@ -1,42 +1,42 @@ // socket setup -import { Server, Socket } from "socket.io"; -import { vote, join } from "./controllers/socketController"; -import { RateLimiterMemory } from "rate-limiter-flexible"; +import { Server, Socket } from 'socket.io' +import { vote, join } from './controllers/socketController' +import { RateLimiterMemory } from 'rate-limiter-flexible' const rateLimiter = new RateLimiterMemory({ points: 5, // 5 points connections - duration: 3, // per second -}); + duration: 3 // per second +}) const io = new Server({ - path: "/socket.io", - cors: { origin: process.env.FRONTEND_URL, credentials: true }, -}); + path: '/socket.io', + cors: { origin: process.env.FRONTEND_URL, credentials: true } +}) // log the socket id when client socket connects for the first time -io.on("connection", (socket: Socket) => { - console.log(`connect: ${socket.id}`); +io.on('connection', (socket: Socket) => { + console.log(`connect: ${socket.id}`) // let the socket join rooms once connected - socket.on("join", async (pollCode: string) => { + socket.on('join', async (pollCode: string) => { try { - await rateLimiter.consume(socket.data.utorid); - await join(socket, pollCode); + await rateLimiter.consume(socket.data.utorid) + await join(socket, pollCode) // let the socket vote in the connected room - socket.on("vote", async (answer: number) => { + socket.on('vote', async (answer: number) => { try { - await rateLimiter.consume(socket.data.utorid); - await vote(socket, answer, socket.data.utorid); + await rateLimiter.consume(socket.data.utorid) + await vote(socket, answer, socket.data.utorid) } catch (err) { - socket.emit("error", { code: 2, message: "retry again later" }); + socket.emit('error', { code: 2, message: 'retry again later' }) } - }); + }) } catch (err) { - socket.emit("error", { code: 2, message: "retry again later" }); + socket.emit('error', { code: 2, message: 'retry again later' }) } - }); -}); + }) +}) -export { io }; +export { io } diff --git a/server/yarn.lock b/server/yarn.lock index 877a989..9e3ae79 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -1434,6 +1434,11 @@ http-errors@~1.7.2: statuses ">= 1.5.0 < 2" toidentifier "1.0.0" +husky@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.1.tgz#511cb3e57de3e3190514ae49ed50f6bc3f50b3e9" + integrity sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw== + iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" From 2fa05aceace5efa5a76e099e17187493a2b4683b Mon Sep 17 00:00:00 2001 From: Ido Date: Sun, 30 Oct 2022 21:49:03 -0400 Subject: [PATCH 17/23] Removed use of any. (#70) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Removed use of any. Referencing issue #63 * Implemented PR type suggestions * –Fixed pull request suggestions. * Fixed middleware sending response * Fixed pull request suggestions * Removed unnecessary undefined from arguments * Changed string check to check for empty strings * Fixed eslint errors --- README.md | 2 ++ server/src/controllers/pollController.ts | 14 +++++++------- server/src/controllers/userController.ts | 4 ++-- server/src/routes/pollRoute.ts | 6 ++++++ server/src/routes/userRoutes.ts | 3 +++ server/src/server.ts | 3 +++ server/src/types/pollController.types.ts | 10 ++++++++++ 7 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 server/src/types/pollController.types.ts diff --git a/README.md b/README.md index 0fd17ca..0fb31a2 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,8 @@ Note: Only the core files & directories are listed below | |── routes | | |── pollRoute.ts | | └── userRoutes.ts +| |── types +| | └── pollController.types.ts | |── redis.ts # Redis related configurations to setup the professor list | |── server.ts | └── socket.ts diff --git a/server/src/controllers/pollController.ts b/server/src/controllers/pollController.ts index 51abe7f..4e84f31 100644 --- a/server/src/controllers/pollController.ts +++ b/server/src/controllers/pollController.ts @@ -62,11 +62,11 @@ async function getStudents (courseCode: string, startTime: Date, endTime: Date) // eslint-disable-next-line no-useless-catch try { const pollDoc = await PollModel.find({ courseCode }) - const promises: Promise[] = [] - const responses: any[] = [] + const promises: Promise[] = [] + const responses: AggregatedStudent[] = [] pollDoc.forEach((element) => { promises.push( - StudentModel.aggregate([ + StudentModel.aggregate([ { $match: { pollId: element._id.toString(), @@ -109,15 +109,15 @@ async function getStudents (courseCode: string, startTime: Date, endTime: Date) } } -async function getPollStatus (pollId: any) { - if (pollId === null || pollId === undefined || typeof pollId !== 'string') { return { status: 400, data: { message: 'Invalid poll Id' } } } +async function getPollStatus (pollId: string) { + if (pollId.trim().length === 0) { return { status: 400, data: { message: 'Invalid poll Id' } } } const result = await client.get(pollId) const pollStarted = result === null ? false : parseInt(result) > 0 return { status: 200, data: { pollStarted } } } -async function getResult (pollId: any) { - if (pollId === null || pollId === undefined || typeof pollId !== 'string') { return { status: 400, data: { message: 'Invalid poll Id' } } } +async function getResult (pollId: string) { + if (pollId.trim().length === 0) { return { status: 400, data: { message: 'Invalid poll Id' } } } const currSequence = await client.get(pollId) const result = await pollResult(pollId, parseInt(currSequence)) return { status: 200, data: { ...result } } diff --git a/server/src/controllers/userController.ts b/server/src/controllers/userController.ts index 02d258d..06d8a24 100644 --- a/server/src/controllers/userController.ts +++ b/server/src/controllers/userController.ts @@ -1,7 +1,7 @@ import { client } from '../redis' -async function getUser (utorid: any) { - if (utorid === null || utorid === undefined || typeof utorid !== 'string') { return { status: 400, data: { message: 'Invalid utorid' } } } +async function getUser (utorid: string) { + if (utorid.trim().length === 0) { return { status: 400, data: { message: 'Invalid utorid' } } } let userType = await client.get(utorid) if (userType == null) userType = 'student' return { status: 200, data: { userType } } diff --git a/server/src/routes/pollRoute.ts b/server/src/routes/pollRoute.ts index 450a34d..bebd377 100644 --- a/server/src/routes/pollRoute.ts +++ b/server/src/routes/pollRoute.ts @@ -62,6 +62,9 @@ pollRouter.get('/students', async (req, res) => { pollRouter.get('/status', async (req, res) => { const { pollId } = req.query try { + if (typeof pollId !== 'string') { + return res.status(400).send({ message: 'Invalid utorid' }) + } const result = await getPollStatus(pollId) return res.status(result.status).send(result.data) } catch (err) { @@ -72,6 +75,9 @@ pollRouter.get('/status', async (req, res) => { pollRouter.get('/result', async (req, res) => { const { pollId } = req.query try { + if (typeof pollId !== 'string') { + return res.status(400).send({ message: 'Invalid utorid' }) + } const result = await getResult(pollId) return res.status(result.status).send(result.data) } catch (err) { diff --git a/server/src/routes/userRoutes.ts b/server/src/routes/userRoutes.ts index cb5d0a9..34f88cf 100644 --- a/server/src/routes/userRoutes.ts +++ b/server/src/routes/userRoutes.ts @@ -5,6 +5,9 @@ const userRouter = Router() userRouter.get('/', async (req, res) => { try { + if (typeof req.headers.utorid !== 'string') { + return res.status(400).send({ message: 'Invalid utorid' }) + } const result = await getUser(req.headers.utorid) return res.status(result.status).send(result.data) } catch (err) { diff --git a/server/src/server.ts b/server/src/server.ts index 4050c02..2d216d0 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -32,6 +32,9 @@ app.use(cookieParser()) app.use('/user', userRouter) app.use(async (req, res, next) => { try { + if (typeof req.headers.utorid !== 'string') { + return next(new Error('Invalid utorid')) + } const userType = await getUser(req.headers.utorid) if (userType.data.userType === 'instructor') next() else next(new Error('Forbidden User')) diff --git a/server/src/types/pollController.types.ts b/server/src/types/pollController.types.ts new file mode 100644 index 0000000..3f885ea --- /dev/null +++ b/server/src/types/pollController.types.ts @@ -0,0 +1,10 @@ +export type AggregatedStudent = { + pollId: string, + courseCode: string, + sequence: number, + utorid: string, + timestamp: string, + pollName: string, + description: string, + answer: number +} From 36aadac7493baf8b31e243aa9eff425103098ff3 Mon Sep 17 00:00:00 2001 From: Ido Date: Thu, 3 Nov 2022 23:07:33 -0400 Subject: [PATCH 18/23] Added missing import statement (#80) * Added missing import statement * Fixed eslint --- server/src/controllers/pollController.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/controllers/pollController.ts b/server/src/controllers/pollController.ts index 4e84f31..39ec28d 100644 --- a/server/src/controllers/pollController.ts +++ b/server/src/controllers/pollController.ts @@ -4,6 +4,7 @@ import { io } from '../socket' import { client } from '../redis' import { customAlphabet } from 'nanoid/async' import { pollResult } from './socketController' +import { AggregatedStudent } from '../types/pollController.types' const nanoid = customAlphabet('qwertyuiopasdfghjklzxcvbnm1234567890', 6) // set poll code expiry to 1 day From 00551340266b75c1f3371b1ce5b78627782b7caa Mon Sep 17 00:00:00 2001 From: Ido Date: Tue, 8 Nov 2022 20:44:00 -0500 Subject: [PATCH 19/23] Changed char set (see #77) (#83) --- server/src/controllers/pollController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/controllers/pollController.ts b/server/src/controllers/pollController.ts index 39ec28d..9f7493f 100644 --- a/server/src/controllers/pollController.ts +++ b/server/src/controllers/pollController.ts @@ -5,7 +5,7 @@ import { client } from '../redis' import { customAlphabet } from 'nanoid/async' import { pollResult } from './socketController' import { AggregatedStudent } from '../types/pollController.types' -const nanoid = customAlphabet('qwertyuiopasdfghjklzxcvbnm1234567890', 6) +const nanoid = customAlphabet('QWERTYUIOPASDFGHJKLZXCVBNM', 6) // set poll code expiry to 1 day const expiry = 60 * 60 * 24 From 3facdf53598fc80fe54599723a9e3fe11c9792cc Mon Sep 17 00:00:00 2001 From: Shubh Bapna <38372682+shubhbapna@users.noreply.github.com> Date: Thu, 22 Dec 2022 19:09:31 -0500 Subject: [PATCH 20/23] moved mongo, redis setup to functions (#84) --- server/src/db/mogoose.ts | 9 ++++++--- server/src/redis.ts | 9 +++++---- server/src/server.ts | 8 ++++---- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/server/src/db/mogoose.ts b/server/src/db/mogoose.ts index 10ca3e2..3b759fa 100644 --- a/server/src/db/mogoose.ts +++ b/server/src/db/mogoose.ts @@ -9,8 +9,11 @@ import { export const PollModel = model('Poll', pollSchema) export const StudentModel = model('Student', studentSchema) -connect(process.env.MONGODB_URL).catch((err) => { - throw err -}) +export async function connectMongo () { + await connect(process.env.MONGODB_URL) + db.on('open', () => { + console.log('Connected to mongo') + }) +} export const db = connection diff --git a/server/src/redis.ts b/server/src/redis.ts index 9a39ad8..d15f7b2 100644 --- a/server/src/redis.ts +++ b/server/src/redis.ts @@ -2,11 +2,12 @@ import { createClient } from 'redis' import fs from 'fs' import path from 'path' import readline from 'readline' + const client = createClient({ url: process.env.REDIS_URL -}); +}) -(async () => { +async function connectRedis () { client.on('error', (err) => console.log('Redis Client Error', err)) await client.connect() @@ -25,6 +26,6 @@ const client = createClient({ console.log(line.trim()) client.set(line.trim(), 'instructor') } -})() +} -export { client } +export { client, connectRedis } diff --git a/server/src/server.ts b/server/src/server.ts index 2d216d0..d0cb645 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -6,13 +6,11 @@ import cors from 'cors' import cookieParser from 'cookie-parser' import { io } from './socket' import pollRouter from './routes/pollRoute' -import { db } from './db/mogoose' +import { connectMongo } from './db/mogoose' import userRouter from './routes/userRoutes' import { getUser } from './controllers/userController' +import { connectRedis } from './redis' dotenv.config() -db.on('open', () => { - console.log('Connected to mongo') -}) // starting the express server const app = express() @@ -46,6 +44,8 @@ app.use('/poll', pollRouter) const server = app.listen(port, () => { console.log('Listening on http://localhost:' + port) + connectRedis() + connectMongo() io.attach(server) io.use(async (socket, next) => { try { From 7d1a8ef160b89cd4cb112188a219eb84d705a02e Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Fri, 6 Jan 2023 03:29:29 -0500 Subject: [PATCH 21/23] Make sure polling results are only sent to instructors (#76) * Make sure polling results are only sent to instructors * Add UserType enum --- server/src/controllers/pollController.ts | 5 +++-- server/src/controllers/socketController.ts | 13 ++++++++++--- server/src/controllers/userController.ts | 10 +++++++--- server/src/redis.ts | 3 ++- server/src/server.ts | 8 +++++++- server/src/types/user.types.ts | 4 ++++ 6 files changed, 33 insertions(+), 10 deletions(-) create mode 100644 server/src/types/user.types.ts diff --git a/server/src/controllers/pollController.ts b/server/src/controllers/pollController.ts index 9f7493f..440a2f5 100644 --- a/server/src/controllers/pollController.ts +++ b/server/src/controllers/pollController.ts @@ -3,8 +3,9 @@ import { PollModel, StudentModel } from '../db/mogoose' import { io } from '../socket' import { client } from '../redis' import { customAlphabet } from 'nanoid/async' -import { pollResult } from './socketController' +import { pollResult, getRoomById } from './socketController' import { AggregatedStudent } from '../types/pollController.types' +import { UserType } from '../types/user.types' const nanoid = customAlphabet('QWERTYUIOPASDFGHJKLZXCVBNM', 6) // set poll code expiry to 1 day @@ -42,7 +43,7 @@ async function changePollStatus (pollId: string, hasStarted: boolean) { EX: expiry }) const result = await pollResult(pollId, newSequence) - io.to(pollId).emit('result', result) + io.to(getRoomById(pollId, UserType.INSTRUCTOR)).emit('result', result) } else { // for every stop make the current counter negative to indicate that it is not an active sequence if (currSequence != null) { diff --git a/server/src/controllers/socketController.ts b/server/src/controllers/socketController.ts index 71b9a39..915b347 100644 --- a/server/src/controllers/socketController.ts +++ b/server/src/controllers/socketController.ts @@ -2,6 +2,11 @@ import { StudentModel } from '../db/mogoose' import { io } from '../socket' import { Socket } from 'socket.io' import { client } from '../redis' +import { UserType } from '../types/user.types' + +function getRoomById (pollId: string, userType?: UserType): string { + if (userType) { return userType + '-' + pollId } else { return pollId.toString() } +} async function join (socket: Socket, pollCode: string) { try { @@ -20,7 +25,9 @@ async function join (socket: Socket, pollCode: string) { const hasStarted = currSequence == null ? false : parseInt(currSequence) > 0 console.log('Has Started', hasStarted) - socket.join(pollId) + socket.join(getRoomById(pollId)) + // allow targeting specific user types + socket.join(getRoomById(pollId, socket.data.userType)) socket.data.pollId = pollId io.to(socket.id).emit('pollStarted', hasStarted) } catch (err) { @@ -82,7 +89,7 @@ async function vote (socket: Socket, answer: number, utorid: string) { { upsert: true } ) pollResult(pollId, parseInt(currSequence)).then((data) => { - io.to(pollId).emit('result', data) + io.to(getRoomById(pollId, UserType.INSTRUCTOR)).emit('result', data) }) io.to(socket.id).emit('ack', answer) return @@ -92,4 +99,4 @@ async function vote (socket: Socket, answer: number, utorid: string) { } } -export { vote, join, pollResult } +export { vote, join, pollResult, getRoomById } diff --git a/server/src/controllers/userController.ts b/server/src/controllers/userController.ts index 06d8a24..63cba24 100644 --- a/server/src/controllers/userController.ts +++ b/server/src/controllers/userController.ts @@ -1,9 +1,13 @@ +import { UserType } from '../types/user.types' import { client } from '../redis' -async function getUser (utorid: string) { +async function getUser (utorid: string): Promise<{ status: number; data: { + message?: string, + userType?: UserType +}}> { if (utorid.trim().length === 0) { return { status: 400, data: { message: 'Invalid utorid' } } } - let userType = await client.get(utorid) - if (userType == null) userType = 'student' + let userType = await client.get(utorid) as UserType + if (userType == null) userType = UserType.STUDENT return { status: 200, data: { userType } } } diff --git a/server/src/redis.ts b/server/src/redis.ts index d15f7b2..982a9d5 100644 --- a/server/src/redis.ts +++ b/server/src/redis.ts @@ -2,6 +2,7 @@ import { createClient } from 'redis' import fs from 'fs' import path from 'path' import readline from 'readline' +import { UserType } from './types/user.types' const client = createClient({ url: process.env.REDIS_URL @@ -24,7 +25,7 @@ async function connectRedis () { for await (const line of rl) { console.log(line.trim()) - client.set(line.trim(), 'instructor') + client.set(line.trim(), UserType.INSTRUCTOR) } } diff --git a/server/src/server.ts b/server/src/server.ts index d0cb645..a499672 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -9,6 +9,7 @@ import pollRouter from './routes/pollRoute' import { connectMongo } from './db/mogoose' import userRouter from './routes/userRoutes' import { getUser } from './controllers/userController' +import { UserType } from './types/user.types' import { connectRedis } from './redis' dotenv.config() @@ -34,7 +35,7 @@ app.use(async (req, res, next) => { return next(new Error('Invalid utorid')) } const userType = await getUser(req.headers.utorid) - if (userType.data.userType === 'instructor') next() + if (userType.data.userType === UserType.INSTRUCTOR) next() else next(new Error('Forbidden User')) } catch (err) { next(new Error('Forbidden User')) @@ -62,4 +63,9 @@ const server = app.listen(port, () => { next(new Error('Not Authorized')) } }) + io.use(async (socket, next) => { + const userType = await getUser(socket.data.utorid) + socket.data.userType = userType.data.userType + next() + }) }) diff --git a/server/src/types/user.types.ts b/server/src/types/user.types.ts new file mode 100644 index 0000000..5339d8f --- /dev/null +++ b/server/src/types/user.types.ts @@ -0,0 +1,4 @@ +export enum UserType { + INSTRUCTOR = 'instructor', + STUDENT = 'student' +} From 1ebc5a2fcc63f0aba2c79c4f02c6ef1f8eb400b2 Mon Sep 17 00:00:00 2001 From: Chris Lim Date: Wed, 11 Jan 2023 00:56:33 -0500 Subject: [PATCH 22/23] Minor changes --- client/src/pages/VotePage.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/client/src/pages/VotePage.tsx b/client/src/pages/VotePage.tsx index 991b9d7..f650b74 100644 --- a/client/src/pages/VotePage.tsx +++ b/client/src/pages/VotePage.tsx @@ -22,7 +22,9 @@ export const VotePage = () => { const [selectedOption, setSelectionOption] = useState(""); const onFocus = () => { - if (document.visibilityState === "visible") document.title = mcsPollVoting; + if (document.visibilityState === "visible") { + document.title = mcsPollVoting; + } }; useEffect(() => { @@ -61,8 +63,10 @@ export const VotePage = () => { if (document.visibilityState === "hidden") { document.title = questionStarted; if (hasAllowedNotif) { - /* first, close old notification to prevent spam */ - if (lastNotif != null) lastNotif.close(); + /* Close old notification to prevent spam */ + if (lastNotif != null) { + lastNotif.close(); + } /* send new notification */ const notification = new Notification("New Question Started!", { icon: "/favicon.ico", From e3b9734018af5f6798e2a62344a09d0a21290ad1 Mon Sep 17 00:00:00 2001 From: Chris Lim Date: Wed, 11 Jan 2023 01:08:56 -0500 Subject: [PATCH 23/23] Revert some changed files --- client/src/axios.ts | 3 +- client/src/socket.ts | 1 - docker-compose.yml | 102 +++++++++++++++++++++---------------------- 3 files changed, 52 insertions(+), 54 deletions(-) diff --git a/client/src/axios.ts b/client/src/axios.ts index 184be26..38cb4f9 100644 --- a/client/src/axios.ts +++ b/client/src/axios.ts @@ -1,6 +1,5 @@ import axios from "axios"; export const instance = axios.create({ - headers: { utorid: "utorid" }, - baseURL: `${process.env.REACT_APP_BACKEND_URL}`, + baseURL: `${process.env.REACT_APP_BACKEND_URL}/api`, }); diff --git a/client/src/socket.ts b/client/src/socket.ts index 13d8911..8e7412e 100644 --- a/client/src/socket.ts +++ b/client/src/socket.ts @@ -1,5 +1,4 @@ import { io } from "socket.io-client"; export const socket = io(`${process.env.REACT_APP_BACKEND_URL}`, { withCredentials: true, - extraHeaders: { utorid: "utorid" }, }); diff --git a/docker-compose.yml b/docker-compose.yml index 8528dc9..5808cec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,57 +29,57 @@ services: volumes: - MongodbVolume:/data/db -# voteapi: -# container_name: voteapi -# build: -# context: ./server -# dockerfile: Dockerfile -# restart: unless-stopped -# networks: -# backend: -# aliases: -# - voteapi -# depends_on: -# - mongodb -# - caching -# ports: -# - "5001:5001" -# env_file: -# - ./server/.env -# environment: -# - TZ=America/New_York -# -# frontend: -# container_name: frontend -# restart: unless-stopped -# build: -# context: ./client -# dockerfile: Dockerfile -# depends_on: -# - voteapi -# networks: -# backend: -# aliases: -# - frontend -# ports: -# - "3001:3001" -# env_file: -# - ./client/.env -# -# nginx-proxy: -# container_name: nginx -# image: nginx -# restart: unless-stopped -# depends_on: -# - frontend -# networks: -# backend: -# aliases: -# - nginx -# ports: -# - "5000:80" -# volumes: -# - ./nginx.conf:/etc/nginx/nginx.conf + voteapi: + container_name: voteapi + build: + context: ./server + dockerfile: Dockerfile + restart: unless-stopped + networks: + backend: + aliases: + - voteapi + depends_on: + - mongodb + - caching + ports: + - "5001:5001" + env_file: + - ./server/.env + environment: + - TZ=America/New_York + + frontend: + container_name: frontend + restart: unless-stopped + build: + context: ./client + dockerfile: Dockerfile + depends_on: + - voteapi + networks: + backend: + aliases: + - frontend + ports: + - "3001:3001" + env_file: + - ./client/.env + + nginx-proxy: + container_name: nginx + image: nginx + restart: unless-stopped + depends_on: + - frontend + networks: + backend: + aliases: + - nginx + ports: + - "5000:80" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf volumes: RedisVolume: