From 1640af25830a5d95e1574ea6ff3eaf90fce403be Mon Sep 17 00:00:00 2001 From: Viktor Ristic Date: Wed, 30 Aug 2023 18:28:48 -0400 Subject: [PATCH 01/45] Setup CaptureFeedback component and styling files --- src/components/CaptureFeedback/CaptureFeedback.css | 3 +++ src/components/CaptureFeedback/CaptureFeedback.jsx | 11 +++++++++++ 2 files changed, 14 insertions(+) create mode 100644 src/components/CaptureFeedback/CaptureFeedback.css create mode 100644 src/components/CaptureFeedback/CaptureFeedback.jsx diff --git a/src/components/CaptureFeedback/CaptureFeedback.css b/src/components/CaptureFeedback/CaptureFeedback.css new file mode 100644 index 0000000..6208ca0 --- /dev/null +++ b/src/components/CaptureFeedback/CaptureFeedback.css @@ -0,0 +1,3 @@ +.feedback-panel { +display: flex; +} \ No newline at end of file diff --git a/src/components/CaptureFeedback/CaptureFeedback.jsx b/src/components/CaptureFeedback/CaptureFeedback.jsx new file mode 100644 index 0000000..742674d --- /dev/null +++ b/src/components/CaptureFeedback/CaptureFeedback.jsx @@ -0,0 +1,11 @@ +import React from "react"; +import "./CaptureFeedback.css" + +export const CaptureFeedback = () => { + + return ( +
+

TEST TEXT HERE

+
+ ) +} \ No newline at end of file From 6fe0a33252aa7d26f08d5d4f6cc2566ab3624d20 Mon Sep 17 00:00:00 2001 From: Viktor Ristic Date: Wed, 30 Aug 2023 18:42:08 -0400 Subject: [PATCH 02/45] Add state management to component --- src/components/CaptureFeedback/CaptureFeedback.jsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/components/CaptureFeedback/CaptureFeedback.jsx b/src/components/CaptureFeedback/CaptureFeedback.jsx index 742674d..4afaf03 100644 --- a/src/components/CaptureFeedback/CaptureFeedback.jsx +++ b/src/components/CaptureFeedback/CaptureFeedback.jsx @@ -3,8 +3,17 @@ import "./CaptureFeedback.css" export const CaptureFeedback = () => { + const [panelActive, setPanelActive] = useState(false); + + const togglePanel = () => { + setPanelActive((prev) => !prev); + }; + return ( -
+
+

TEST TEXT HERE

) From c5dc75ef2b85f844b70e644341b84943a683ee4e Mon Sep 17 00:00:00 2001 From: Viktor Ristic Date: Wed, 30 Aug 2023 19:11:57 -0400 Subject: [PATCH 03/45] Add styling to CaptureFeedback panel --- src/components/CaptureFeedback/CaptureFeedback.css | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/CaptureFeedback/CaptureFeedback.css b/src/components/CaptureFeedback/CaptureFeedback.css index 6208ca0..4f18659 100644 --- a/src/components/CaptureFeedback/CaptureFeedback.css +++ b/src/components/CaptureFeedback/CaptureFeedback.css @@ -1,3 +1,14 @@ .feedback-panel { -display: flex; + position: fixed; + top: -100%; /* Initial position outside the viewport */ + right: 0; + width: 300px; + height: 100%; + background-color: white; + box-shadow: -2px 0px 4px rgba(0, 0, 0, 0.2); + transition: top 0.3s ease-in-out; +} + +.feedback-panel.active { + top: 0; /* Slide the panel into view */ } \ No newline at end of file From 7cb12b86720b8cf19ea2a56c35a0f69442878adc Mon Sep 17 00:00:00 2001 From: Viktor Ristic Date: Wed, 30 Aug 2023 20:24:48 -0400 Subject: [PATCH 04/45] Update panel width --- src/components/CaptureFeedback/CaptureFeedback.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CaptureFeedback/CaptureFeedback.css b/src/components/CaptureFeedback/CaptureFeedback.css index 4f18659..1e1ac1a 100644 --- a/src/components/CaptureFeedback/CaptureFeedback.css +++ b/src/components/CaptureFeedback/CaptureFeedback.css @@ -2,7 +2,7 @@ position: fixed; top: -100%; /* Initial position outside the viewport */ right: 0; - width: 300px; + width: 400px; height: 100%; background-color: white; box-shadow: -2px 0px 4px rgba(0, 0, 0, 0.2); From ca1224cd0fd842c54b8112851f5116d33db36022 Mon Sep 17 00:00:00 2001 From: Viktor Ristic Date: Wed, 30 Aug 2023 20:25:50 -0400 Subject: [PATCH 05/45] Update CaptureFeedback state management and pass props --- .../CaptureFeedback/CaptureFeedback.jsx | 35 +++++++++---------- src/components/Dropdown/Dropdown.js | 19 +++++++--- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/components/CaptureFeedback/CaptureFeedback.jsx b/src/components/CaptureFeedback/CaptureFeedback.jsx index 4afaf03..31100d2 100644 --- a/src/components/CaptureFeedback/CaptureFeedback.jsx +++ b/src/components/CaptureFeedback/CaptureFeedback.jsx @@ -1,20 +1,19 @@ -import React from "react"; -import "./CaptureFeedback.css" +import { React } from 'react'; +import PropTypes from 'prop-types'; +import './CaptureFeedback.css'; -export const CaptureFeedback = () => { +export function CaptureFeedback({ isActive, onClose }) { + return ( +
+ +

TEST TEXT HERE

+
+ ); +} - const [panelActive, setPanelActive] = useState(false); - - const togglePanel = () => { - setPanelActive((prev) => !prev); - }; - - return ( -
- -

TEST TEXT HERE

-
- ) -} \ No newline at end of file +CaptureFeedback.propTypes = { + isActive: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, +}; diff --git a/src/components/Dropdown/Dropdown.js b/src/components/Dropdown/Dropdown.js index 1c8fc53..e6de976 100644 --- a/src/components/Dropdown/Dropdown.js +++ b/src/components/Dropdown/Dropdown.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import toast, { Toaster } from 'react-hot-toast'; import { useNavigate } from 'react-router-dom'; import Box from '@mui/material/Box'; @@ -15,11 +15,12 @@ import LoginIcon from '@mui/icons-material/Login'; import HelpIcon from '@mui/icons-material/Help'; import MessageIcon from '@mui/icons-material/Message'; import ManageAccountsIcon from '@mui/icons-material/ManageAccounts'; -import { useEffect } from 'react'; +import { CaptureFeedback } from '../CaptureFeedback/CaptureFeedback'; // export default function Dropdown() { const [anchorEl, setAnchorEl] = useState(null); const [loggedIn, setLoggedIn] = useState(false); // Change the state name to 'loggedIn' + const [feedbackVisible, setFeedbackVisible] = useState(false); // Toggle visibility of feedback panel const open = Boolean(anchorEl); const navigate = useNavigate(); @@ -58,6 +59,11 @@ export default function Dropdown() { navigate('/profile'); } + function toggleFeedback() { + setFeedbackVisible((prevFeedbackVisible) => !prevFeedbackVisible); + handleClose(); + } + return (   Help - +   Feedback @@ -154,6 +159,12 @@ export default function Dropdown() { )} + {feedbackVisible && ( + + )} ); } From b1bceaf6438cf7e00cbe7d5d8035d29fa7d83891 Mon Sep 17 00:00:00 2001 From: Viktor Ristic Date: Wed, 30 Aug 2023 21:02:21 -0400 Subject: [PATCH 06/45] Update styling --- src/components/CaptureFeedback/CaptureFeedback.css | 11 ++++++++++- src/components/CaptureFeedback/CaptureFeedback.jsx | 1 - 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/CaptureFeedback/CaptureFeedback.css b/src/components/CaptureFeedback/CaptureFeedback.css index 1e1ac1a..b76f616 100644 --- a/src/components/CaptureFeedback/CaptureFeedback.css +++ b/src/components/CaptureFeedback/CaptureFeedback.css @@ -1,14 +1,23 @@ .feedback-panel { position: fixed; + padding: 10px; top: -100%; /* Initial position outside the viewport */ right: 0; width: 400px; height: 100%; - background-color: white; + background-color: rgb(225, 219, 219); box-shadow: -2px 0px 4px rgba(0, 0, 0, 0.2); transition: top 0.3s ease-in-out; } +.close-button { + border: none; + font-weight: bold; + background-color: rgb(225, 219, 219); + font-size: 1.2em; /* Adjust as needed */ + cursor: pointer; +} + .feedback-panel.active { top: 0; /* Slide the panel into view */ } \ No newline at end of file diff --git a/src/components/CaptureFeedback/CaptureFeedback.jsx b/src/components/CaptureFeedback/CaptureFeedback.jsx index 31100d2..ce4f385 100644 --- a/src/components/CaptureFeedback/CaptureFeedback.jsx +++ b/src/components/CaptureFeedback/CaptureFeedback.jsx @@ -8,7 +8,6 @@ export function CaptureFeedback({ isActive, onClose }) { -

TEST TEXT HERE

); } From 6a82a0d159fa763d717e894ed5d2d68bbf49be61 Mon Sep 17 00:00:00 2001 From: Viktor Ristic Date: Thu, 31 Aug 2023 20:16:27 -0400 Subject: [PATCH 07/45] Convert panel to modal --- package-lock.json | 37 +++++++++++++++++++ package.json | 1 + .../CaptureFeedback/CaptureFeedback.css | 33 ++++++++++------- .../CaptureFeedback/CaptureFeedback.jsx | 16 ++++++-- src/components/Dropdown/Dropdown.js | 2 +- 5 files changed, 71 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index cb9ccbd..6b04044 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "react-dom": "^18.2.0", "react-hot-toast": "^2.4.1", "react-jwt": "^1.2.0", + "react-modal": "^3.16.1", "react-router": "^6.13.0", "react-router-dom": "^6.13.0", "react-scripts": "5.0.1", @@ -8444,6 +8445,11 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/exenv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", + "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==" + }, "node_modules/exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -14933,6 +14939,29 @@ "react": "^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "node_modules/react-modal": { + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.16.1.tgz", + "integrity": "sha512-VStHgI3BVcGo7OXczvnJN7yT2TWHJPDXZWyI/a0ssFNhGZWsPmB8cF0z33ewDXq4VfYMO1vXgiv/g8Nj9NDyWg==", + "dependencies": { + "exenv": "^1.2.0", + "prop-types": "^15.7.2", + "react-lifecycles-compat": "^3.0.0", + "warning": "^4.0.3" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18", + "react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18" + } + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -17187,6 +17216,14 @@ "makeerror": "1.0.12" } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", diff --git a/package.json b/package.json index 28f47f4..1e19d1d 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "react-dom": "^18.2.0", "react-hot-toast": "^2.4.1", "react-jwt": "^1.2.0", + "react-modal": "^3.16.1", "react-router": "^6.13.0", "react-router-dom": "^6.13.0", "react-scripts": "5.0.1", diff --git a/src/components/CaptureFeedback/CaptureFeedback.css b/src/components/CaptureFeedback/CaptureFeedback.css index b76f616..8459b21 100644 --- a/src/components/CaptureFeedback/CaptureFeedback.css +++ b/src/components/CaptureFeedback/CaptureFeedback.css @@ -1,23 +1,28 @@ -.feedback-panel { +.overlay { position: fixed; - padding: 10px; - top: -100%; /* Initial position outside the viewport */ + top: 0; + left: 0; right: 0; - width: 400px; - height: 100%; - background-color: rgb(225, 219, 219); - box-shadow: -2px 0px 4px rgba(0, 0, 0, 0.2); - transition: top 0.3s ease-in-out; + bottom: 0; + background-color: rgba(0, 0, 0, 0.7); +} + +.feedback-modal { + position: absolute; + top: 50%; + left: 50%; + width: 75%; + height: 75%; + transform: translate(-50%, -50%); + background: white; + padding: 20px; + border-radius: 4px; } .close-button { border: none; font-weight: bold; - background-color: rgb(225, 219, 219); - font-size: 1.2em; /* Adjust as needed */ + font-size: 1.5em; cursor: pointer; + background: none; } - -.feedback-panel.active { - top: 0; /* Slide the panel into view */ -} \ No newline at end of file diff --git a/src/components/CaptureFeedback/CaptureFeedback.jsx b/src/components/CaptureFeedback/CaptureFeedback.jsx index ce4f385..8cead26 100644 --- a/src/components/CaptureFeedback/CaptureFeedback.jsx +++ b/src/components/CaptureFeedback/CaptureFeedback.jsx @@ -1,14 +1,24 @@ -import { React } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; +import Modal from 'react-modal'; import './CaptureFeedback.css'; +//Bind modal to your app +Modal.setAppElement('#root'); + export function CaptureFeedback({ isActive, onClose }) { return ( -
+ -
+ ); } diff --git a/src/components/Dropdown/Dropdown.js b/src/components/Dropdown/Dropdown.js index e6de976..e55e0cd 100644 --- a/src/components/Dropdown/Dropdown.js +++ b/src/components/Dropdown/Dropdown.js @@ -20,7 +20,7 @@ import { CaptureFeedback } from '../CaptureFeedback/CaptureFeedback'; // export default function Dropdown() { const [anchorEl, setAnchorEl] = useState(null); const [loggedIn, setLoggedIn] = useState(false); // Change the state name to 'loggedIn' - const [feedbackVisible, setFeedbackVisible] = useState(false); // Toggle visibility of feedback panel + const [feedbackVisible, setFeedbackVisible] = useState(false); // Toggle visibility of feedback modal const open = Boolean(anchorEl); const navigate = useNavigate(); From 048c51ca39bf95595081a15adb7b470437555243 Mon Sep 17 00:00:00 2001 From: Viktor Ristic Date: Thu, 31 Aug 2023 20:43:43 -0400 Subject: [PATCH 08/45] Add Formik form to modal --- package-lock.json | 87 ++++++++++++++++++- package.json | 4 +- .../CaptureFeedback/CaptureFeedback.css | 32 ++++++- .../CaptureFeedback/CaptureFeedback.jsx | 48 +++++++++- 4 files changed, 167 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6b04044..a687f95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.4.3", "axios": "^1.4.0", + "formik": "^2.4.3", "isbn-validate": "^1.0.3", "lodash": "^4.17.21", "material-ui-popup-state": "^5.0.9", @@ -32,7 +33,8 @@ "react-scripts": "5.0.1", "react-simple-star-rating": "^5.1.7", "styled-components": "^5.3.11", - "web-vitals": "^3.3.2" + "web-vitals": "^3.3.2", + "yup": "^1.2.0" }, "devDependencies": { "eslint": "^8.45.0", @@ -8897,6 +8899,37 @@ "node": ">= 6" } }, + "node_modules/formik": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.3.tgz", + "integrity": "sha512-2Dy79Szw3zlXmZiokUdKsn+n1ow4G8hRrC/n92cOWHNTWXCRpQXlyvz6HcjW7aSQZrldytvDOavYjhfmDnUq8Q==", + "funding": [ + { + "type": "individual", + "url": "https://opencollective.com/formik" + } + ], + "dependencies": { + "deepmerge": "^2.1.1", + "hoist-non-react-statics": "^3.3.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "react-fast-compare": "^2.0.1", + "tiny-warning": "^1.0.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/formik/node_modules/deepmerge": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", + "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -12328,6 +12361,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -14630,6 +14668,11 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/property-expr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz", + "integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -14905,6 +14948,11 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, + "node_modules/react-fast-compare": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", + "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" + }, "node_modules/react-hot-toast": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz", @@ -16756,6 +16804,16 @@ "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "node_modules/titleize": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", @@ -16800,6 +16858,11 @@ "node": ">=0.6" } }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" + }, "node_modules/tough-cookie": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", @@ -18110,6 +18173,28 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/yup": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.2.0.tgz", + "integrity": "sha512-PPqYKSAXjpRCgLgLKVGPA33v5c/WgEx3wi6NFjIiegz90zSwyMpvTFp/uGcVnnbx6to28pgnzp/q8ih3QRjLMQ==", + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } + }, + "node_modules/yup/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 1e19d1d..54a3d2a 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.4.3", "axios": "^1.4.0", + "formik": "^2.4.3", "isbn-validate": "^1.0.3", "lodash": "^4.17.21", "material-ui-popup-state": "^5.0.9", @@ -26,7 +27,8 @@ "react-scripts": "5.0.1", "react-simple-star-rating": "^5.1.7", "styled-components": "^5.3.11", - "web-vitals": "^3.3.2" + "web-vitals": "^3.3.2", + "yup": "^1.2.0" }, "scripts": { "start": "react-scripts start", diff --git a/src/components/CaptureFeedback/CaptureFeedback.css b/src/components/CaptureFeedback/CaptureFeedback.css index 8459b21..4fecd61 100644 --- a/src/components/CaptureFeedback/CaptureFeedback.css +++ b/src/components/CaptureFeedback/CaptureFeedback.css @@ -1,3 +1,4 @@ +/* Modal Styling */ .overlay { position: fixed; top: 0; @@ -16,7 +17,7 @@ transform: translate(-50%, -50%); background: white; padding: 20px; - border-radius: 4px; + border-radius: 10px; } .close-button { @@ -26,3 +27,32 @@ cursor: pointer; background: none; } + +/* Form Styling */ +.feedback-textarea { + width: 100%; + height: 100px; + margin-bottom: 10px; + padding: 10px; + border-radius: 5px; + border: 1px solid #ccc; +} + +.button-group { + display: flex; + justify-content: space-between; +} + +.submit-button, .email-button { + padding: 10px 20px; + border: none; + border-radius: 5px; + background-color: #007BFF; + color: white; + cursor: pointer; +} + +.email-button { + background-color: #28a745; +} + diff --git a/src/components/CaptureFeedback/CaptureFeedback.jsx b/src/components/CaptureFeedback/CaptureFeedback.jsx index 8cead26..4a1adb5 100644 --- a/src/components/CaptureFeedback/CaptureFeedback.jsx +++ b/src/components/CaptureFeedback/CaptureFeedback.jsx @@ -1,11 +1,17 @@ import React from 'react'; import PropTypes from 'prop-types'; import Modal from 'react-modal'; +import { Formik, Form, Field } from 'formik'; +import * as Yup from 'yup'; import './CaptureFeedback.css'; -//Bind modal to your app +// Bind modal to your app Modal.setAppElement('#root'); +const validationSchema = Yup.object({ + feedback: Yup.string().required('Feedback is required'), +}); + export function CaptureFeedback({ isActive, onClose }) { return ( X +

+ We appreciate your feedback.

Please let us know how we + can improve. +

+ { + // Send feedback to backend here + + setSubmitting(false); + onClose(); + }} + > + {({ isSubmitting }) => ( +
+ +
+ + +
+ + )} +
); } From f5c860b2742b5173090d8058dfe7591c67a39608 Mon Sep 17 00:00:00 2001 From: Viktor Ristic Date: Thu, 31 Aug 2023 21:24:33 -0400 Subject: [PATCH 09/45] Update modal to prompt user with two feedback flows --- .../CaptureFeedback/CaptureFeedback.css | 13 ++ .../CaptureFeedback/CaptureFeedback.jsx | 158 +++++++++++++----- 2 files changed, 131 insertions(+), 40 deletions(-) diff --git a/src/components/CaptureFeedback/CaptureFeedback.css b/src/components/CaptureFeedback/CaptureFeedback.css index 4fecd61..ddad98c 100644 --- a/src/components/CaptureFeedback/CaptureFeedback.css +++ b/src/components/CaptureFeedback/CaptureFeedback.css @@ -29,6 +29,17 @@ } /* Form Styling */ + +.modal-form { + width: 100%; + height: 100%; + padding: 10%; +} + +.feedback-modal h2 { + text-align: center; +} + .feedback-textarea { width: 100%; height: 100px; @@ -38,6 +49,8 @@ border: 1px solid #ccc; } + + .button-group { display: flex; justify-content: space-between; diff --git a/src/components/CaptureFeedback/CaptureFeedback.jsx b/src/components/CaptureFeedback/CaptureFeedback.jsx index 4a1adb5..6204c45 100644 --- a/src/components/CaptureFeedback/CaptureFeedback.jsx +++ b/src/components/CaptureFeedback/CaptureFeedback.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; import Modal from 'react-modal'; import { Formik, Form, Field } from 'formik'; @@ -8,11 +8,24 @@ import './CaptureFeedback.css'; // Bind modal to your app Modal.setAppElement('#root'); -const validationSchema = Yup.object({ +const feedbackValidationSchema = Yup.object({ feedback: Yup.string().required('Feedback is required'), + rating: Yup.number().required('Rating is required'), +}); + +const emailValidationSchema = Yup.object({ + email: Yup.string().email('Invalid email address').required('Required'), + message: Yup.string().required('Message is required'), + rating: Yup.number().required('Rating is required'), }); export function CaptureFeedback({ isActive, onClose }) { + const [formToShow, setFormToShow] = useState('initial'); + + const handleFormSwitch = (formType) => { + setFormToShow(formType); + }; + return ( X -

- We appreciate your feedback.

Please let us know how we - can improve. -

- { - // Send feedback to backend here +
+

+ We appreciate your feedback.

Please let us know + how we can improve. +

+ {formToShow === 'initial' && ( +
+ + +
+ )} + {formToShow === 'feedback' && ( + { + // Send feedback to backend here + + setSubmitting(false); + onClose(); + }} + > + {({ isSubmitting }) => ( +
+ + +
+ + +
+ + )} +
+ )} + {formToShow === 'email' && ( + { + // Send email to backend here - setSubmitting(false); - onClose(); - }} - > - {({ isSubmitting }) => ( -
- -
- - -
- + setSubmitting(false); + onClose(); + }} + > + {({ isSubmitting }) => ( +
+ + + +
+ + +
+ + )} +
)} - +
); } From f61c78bbfe861f9727e67d03debb8a23e704ae5e Mon Sep 17 00:00:00 2001 From: Viktor Ristic Date: Thu, 31 Aug 2023 22:32:25 -0400 Subject: [PATCH 10/45] Add star rating functionality --- .../CaptureFeedback/CaptureFeedback.jsx | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/components/CaptureFeedback/CaptureFeedback.jsx b/src/components/CaptureFeedback/CaptureFeedback.jsx index 6204c45..cc4b2f6 100644 --- a/src/components/CaptureFeedback/CaptureFeedback.jsx +++ b/src/components/CaptureFeedback/CaptureFeedback.jsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import Modal from 'react-modal'; import { Formik, Form, Field } from 'formik'; import * as Yup from 'yup'; +import { Rating } from 'react-simple-star-rating'; import './CaptureFeedback.css'; // Bind modal to your app @@ -21,11 +22,17 @@ const emailValidationSchema = Yup.object({ export function CaptureFeedback({ isActive, onClose }) { const [formToShow, setFormToShow] = useState('initial'); + const [rating, setRating] = useState(0); const handleFormSwitch = (formType) => { + setRating(0); setFormToShow(formType); }; + const onPointerMove = (value, index) => { + setRating(value); + }; + return ( -
+
)} From 6ce564765b016e067aff6cd39adfab1aa424f655 Mon Sep 17 00:00:00 2001 From: Viktor Ristic Date: Thu, 31 Aug 2023 22:58:41 -0400 Subject: [PATCH 11/45] Update forms styling --- .../CaptureFeedback/CaptureFeedback.css | 29 ++++++++-------- .../CaptureFeedback/CaptureFeedback.jsx | 33 ++++++++++--------- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/src/components/CaptureFeedback/CaptureFeedback.css b/src/components/CaptureFeedback/CaptureFeedback.css index ddad98c..6d06e41 100644 --- a/src/components/CaptureFeedback/CaptureFeedback.css +++ b/src/components/CaptureFeedback/CaptureFeedback.css @@ -28,16 +28,16 @@ background: none; } -/* Form Styling */ - +/* INITIAL Form Styling */ .modal-form { width: 100%; height: 100%; - padding: 10%; + padding: 4%; } .feedback-modal h2 { text-align: center; + margin-bottom: 10%; } .feedback-textarea { @@ -47,25 +47,24 @@ padding: 10px; border-radius: 5px; border: 1px solid #ccc; + resize: none; } - - .button-group { display: flex; - justify-content: space-between; + justify-content: space-around; } -.submit-button, .email-button { - padding: 10px 20px; - border: none; - border-radius: 5px; - background-color: #007BFF; - color: white; - cursor: pointer; +/* DIRECT or EMAIL forms styling */ +.feedback-textarea { + resize: none; } -.email-button { - background-color: #28a745; +.rating-container { + display: flex; + justify-content: center; + margin-bottom: 20px; } + + diff --git a/src/components/CaptureFeedback/CaptureFeedback.jsx b/src/components/CaptureFeedback/CaptureFeedback.jsx index cc4b2f6..6bc6087 100644 --- a/src/components/CaptureFeedback/CaptureFeedback.jsx +++ b/src/components/CaptureFeedback/CaptureFeedback.jsx @@ -50,7 +50,7 @@ export function CaptureFeedback({ isActive, onClose }) { how we can improve. {formToShow === 'initial' && ( -
+
@@ -59,6 +59,7 @@ export function CaptureFeedback({ isActive, onClose }) {
)} + {/* DIRECT FEEDBACK FORM */} {formToShow === 'feedback' && ( - +
+ +
@@ -49,6 +54,7 @@ export function CaptureFeedback({ isActive, onClose }) { We appreciate your feedback.

Please let us know how we can improve. + {/* INITIAL FORM */} {formToShow === 'initial' && (
-
-

- We appreciate your feedback.

Please let us know - how we can improve. -

- {/* INITIAL FORM */} - {formToShow === 'initial' && ( -
- - -
- )} - {/* DIRECT FEEDBACK FORM */} - {formToShow === 'feedback' && ( - { - //Get token and user info - const token = localStorage.getItem('token'); - const user = JSON.parse( - localStorage.getItem('user') - ); + <> + + {/* Modal close button */} + +
+

+ We appreciate your feedback.

Please let us + know how we can improve. +

+ {/* INITIAL FORM */} + {formToShow === 'initial' && ( +
+ + +
+ )} + {/* DIRECT FEEDBACK FORM */} + {formToShow === 'feedback' && ( + { + //Get token and user info + const token = localStorage.getItem('token'); + const user = JSON.parse( + localStorage.getItem('user') + ); - if (token && user) { - // User logged in and user details are available - const userId = user._id; - axios - .post( - 'http://localhost:3001/api/user/feedback/new', - { - feedback: values.feedback, - rating: values.rating, - author: userId, - }, - { - headers: { - 'Content-Type': - 'application/json', - Authorization: `Bearer ${token}`, + if (token && user) { + // User logged in and user details are available + const userId = user._id; + axios + .post( + 'http://localhost:3001/api/user/feedback/new', + { + feedback: values.feedback, + rating: values.rating, + author: userId, }, - } - ) - .then((response) => { - // HANDLE SUCCESSFUL NOTIFICATION HERE - }) - .then(() => { - setSubmitting(false); - onClose(); - }) - .catch((error) => { - console.error( - 'There was an error submitting the feedback', - error - ); - }); - } else { - // User is not logged in or user details are not available - console.error( - 'User is not authenticated or user details are missing.' + { + headers: { + 'Content-Type': + 'application/json', + Authorization: `Bearer ${token}`, + }, + } + ) + .then((response) => {}) + .then(() => { + setSubmitting(false); + onClose(); + toast.success( + 'Thank you, your feedback has been submitted!' + ); + }) + .catch((error) => { + console.error( + 'There was an error submitting the feedback', + error + ); + }); + } else { + // User is not logged in or user details are not available + console.error( + 'User is not authenticated or user details are missing.' + ); + } + }} + > + {({ isSubmitting, setFieldValue }) => ( + + +
+
+ { + setRating(value); + setFieldValue( + 'rating', + value + ); + }} + onPointerMove={onPointerMove} + initialValue={rating} + /> +
+
+
+ + +
+ + )} +
+ )} + {/* EMAIL FEEDBACK FORM */} + {formToShow === 'email' && ( + { + const token = localStorage.getItem('token'); + const user = JSON.parse( + localStorage.getItem('user') ); - } - }} - > - {({ isSubmitting, setFieldValue }) => ( -
- -
+ const userId = user._id; + const userEmail = user.email; + const username = user.username; + + if (token && user) { + // User logged in and user details are available + axios + .post( + 'http://localhost:3001/api/user/feedback/mail', + { + feedback: values.feedback, + rating: values.rating, + userId: userId, + userEmail: userEmail, + username: username, + }, + { + headers: { + 'Content-Type': + 'application/json', + Authorization: `Bearer ${token}`, + }, + } + ) + .then((response) => {}) + .then(() => { + setSubmitting(false); + onClose(); + toast.success( + 'Thank you, your feedback has been submitted!' + ); + }) + .catch((error) => { + console.error( + 'There was an error submitting the feedback', + error + ); + }); + } else { + // User is not logged in or user details are not available + console.error( + 'User is not authenticated or user details are missing.' + ); + } + }} + > + {({ isSubmitting, setFieldValue }) => ( + +
{ setRating(value); setFieldValue('rating', value); @@ -135,125 +248,33 @@ export function CaptureFeedback({ isActive, onClose }) { initialValue={rating} />
-
-
- - -
- - )} -
- )} - {/* EMAIL FEEDBACK FORM */} - {formToShow === 'email' && ( - { - const token = localStorage.getItem('token'); - const user = JSON.parse( - localStorage.getItem('user') - ); - const userId = user._id; - const userEmail = user.email; - const username = user.username; - - if (token && user) { - // User logged in and user details are available - axios - .post( - 'http://localhost:3001/api/user/feedback/mail', - { - feedback: values.feedback, - rating: values.rating, - userId: userId, - userEmail: userEmail, - username: username, - }, - { - headers: { - 'Content-Type': - 'application/json', - Authorization: `Bearer ${token}`, - }, - } - ) - .then((response) => { - // HANDLE SUCCESS NOTIFICATION HERE - }) - .then(() => { - setSubmitting(false); - onClose(); - }) - .catch((error) => { - console.error( - 'There was an error submitting the feedback', - error - ); - }); - } else { - // User is not logged in or user details are not available - console.error( - 'User is not authenticated or user details are missing.' - ); - } - }} - > - {({ isSubmitting, setFieldValue }) => ( -
- -
- { - setRating(value); - setFieldValue('rating', value); - }} - onPointerMove={onPointerMove} - initialValue={rating} - /> -
-
- - -
- - )} -
- )} -
-
+
+ + +
+ + )} +
+ )} +
+ + + ); } From cc5ebd453bea15e0555445a8c9be21a0a449892d Mon Sep 17 00:00:00 2001 From: Viktor Ristic Date: Sun, 10 Sep 2023 00:13:43 -0400 Subject: [PATCH 36/45] Modularize feedback code --- src/components/Dropdown/Dropdown.js | 8 +- src/components/Feedback/CaptureFeedback.jsx | 74 +++++ src/components/Feedback/DirectFeedback.jsx | 130 ++++++++ src/components/Feedback/EmailFeedback.jsx | 135 +++++++++ src/components/Feedback/Feedback.css | 69 +++++ .../Feedback/InitialFeedbackPrompt.jsx | 284 ++++++++++++++++++ 6 files changed, 696 insertions(+), 4 deletions(-) create mode 100644 src/components/Feedback/CaptureFeedback.jsx create mode 100644 src/components/Feedback/DirectFeedback.jsx create mode 100644 src/components/Feedback/EmailFeedback.jsx create mode 100644 src/components/Feedback/Feedback.css create mode 100644 src/components/Feedback/InitialFeedbackPrompt.jsx diff --git a/src/components/Dropdown/Dropdown.js b/src/components/Dropdown/Dropdown.js index 6216d27..fc29cc2 100644 --- a/src/components/Dropdown/Dropdown.js +++ b/src/components/Dropdown/Dropdown.js @@ -15,7 +15,7 @@ import LoginIcon from '@mui/icons-material/Login'; import HelpIcon from '@mui/icons-material/Help'; import MessageIcon from '@mui/icons-material/Message'; import ManageAccountsIcon from '@mui/icons-material/ManageAccounts'; -import { CaptureFeedback } from '../CaptureFeedback/CaptureFeedback'; // +import { CaptureFeedback } from '../Feedback/CaptureFeedback'; // export default function Dropdown() { const [anchorEl, setAnchorEl] = useState(null); @@ -60,7 +60,7 @@ export default function Dropdown() { } // Toggle CaptureFeedback modal component visibility - function toggleFeedback() { + function toggleFeedbackModal() { setFeedbackVisible((prevFeedbackVisible) => !prevFeedbackVisible); handleClose(); } @@ -124,7 +124,7 @@ export default function Dropdown() {   Help - +   Feedback @@ -164,7 +164,7 @@ export default function Dropdown() { {feedbackVisible && ( )} diff --git a/src/components/Feedback/CaptureFeedback.jsx b/src/components/Feedback/CaptureFeedback.jsx new file mode 100644 index 0000000..8420fac --- /dev/null +++ b/src/components/Feedback/CaptureFeedback.jsx @@ -0,0 +1,74 @@ +import React, { useState } from 'react'; +import Modal from 'react-modal'; +import PropTypes from 'prop-types'; +import { Toaster } from 'react-hot-toast'; +import DirectFeedback from './DirectFeedback'; +import EmailFeedback from './EmailFeedback'; + +Modal.setAppElement('#root'); + +export function CaptureFeedback({ isActive, onClose }) { + const [formToShow, setFormToShow] = useState('initial'); + const [rating, setRating] = useState(0); + + const handleFormSwitch = (formType) => { + setRating(0); + setFormToShow(formType); + }; + + const onPointerMove = (value) => { + setRating(value); + }; + + return ( + + + +

+ We appreciate your feedback. Please let us know how we can + improve. +

+ {formToShow === 'initial' && ( +
+ + +
+ )} + {formToShow === 'feedback' && ( + + )} + {formToShow === 'email' && ( + + )} +
+ +
+ ); +} + +CaptureFeedback.propTypes = { + isActive: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, +}; diff --git a/src/components/Feedback/DirectFeedback.jsx b/src/components/Feedback/DirectFeedback.jsx new file mode 100644 index 0000000..3ac3657 --- /dev/null +++ b/src/components/Feedback/DirectFeedback.jsx @@ -0,0 +1,130 @@ +import React from 'react'; +import { Formik, Form, Field } from 'formik'; +import { Rating } from 'react-simple-star-rating'; +import * as Yup from 'yup'; +import axios from 'axios'; +import PropTypes from 'prop-types'; +import toast, { Toaster } from 'react-hot-toast'; +import './Feedback.css'; + +const feedbackValidationSchema = Yup.object().shape({ + feedback: Yup.string() + .trim() + .required('Feedback is required') + .max(2000, 'Feedback should not exceed 2000 characters'), + rating: Yup.number() + .required('Rating is required') + .min(0, 'Rating should be at least 0') + .max(5, 'Rating should not exceed 5'), +}); + +export default function DirectFeedback({ + handleFormSwitch, + rating, + onPointerMove, + setRating, + onClose, +}) { + return ( + + { + //Get token and user info + const token = localStorage.getItem('token'); + const user = JSON.parse(localStorage.getItem('user')); + + if (token && user) { + // User logged in and user details are available + const userId = user._id; + axios + .post( + 'http://localhost:3001/api/user/feedback/new', + { + feedback: values.feedback, + rating: values.rating, + author: userId, + }, + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + } + ) + .then((response) => {}) + .then(() => { + setSubmitting(false); + onClose(); + toast.success( + 'Thank you, your feedback has been submitted!' + ); + }) + .catch((error) => { + console.error( + 'There was an error submitting the feedback', + error + ); + }); + } else { + // User is not logged in or user details are not available + console.error( + 'User is not authenticated or user details are missing.' + ); + } + }} + > + {({ isSubmitting, setFieldValue }) => ( +
+ +
+
+ { + setRating(value); + setFieldValue('rating', value); + }} + onPointerMove={onPointerMove} + initialValue={rating} + /> +
+
+
+ + +
+ + )} +
+ +
+ ); +} + +DirectFeedback.propTypes = { + handleFormSwitch: PropTypes.func.isRequired, + rating: PropTypes.number.isRequired, + onPointerMove: PropTypes.func.isRequired, + setRating: PropTypes.func.isRequired, + onClose: PropTypes.func.isRequired, +}; diff --git a/src/components/Feedback/EmailFeedback.jsx b/src/components/Feedback/EmailFeedback.jsx new file mode 100644 index 0000000..3353e4b --- /dev/null +++ b/src/components/Feedback/EmailFeedback.jsx @@ -0,0 +1,135 @@ +import React from 'react'; +import { Formik, Form, Field } from 'formik'; +import { Rating } from 'react-simple-star-rating'; +import * as Yup from 'yup'; +import axios from 'axios'; +import PropTypes from 'prop-types'; +import toast, { Toaster } from 'react-hot-toast'; +import './Feedback.css'; + +const feedbackValidationSchema = Yup.object().shape({ + feedback: Yup.string() + .trim() + .required('Feedback is required') + .max(2000, 'Feedback should not exceed 2000 characters'), + rating: Yup.number() + .required('Rating is required') + .min(0, 'Rating should be at least 0') + .max(5, 'Rating should not exceed 5'), +}); + +export default function EmailFeedback({ + handleFormSwitch, + rating, + onPointerMove, + setRating, + onClose, +}) { + return ( + + { + const token = localStorage.getItem('token'); + const user = JSON.parse(localStorage.getItem('user')); + const userId = user._id; + const userEmail = user.email; + const username = user.username; + + if (token && user) { + // User logged in and user details are available + axios + .post( + 'http://localhost:3001/api/user/feedback/mail', + { + feedback: values.feedback, + rating: values.rating, + userId: userId, + userEmail: userEmail, + username: username, + }, + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + } + ) + .then((response) => {}) + .then(() => { + setSubmitting(false); + onClose(); + toast.success( + 'Thank you, your feedback has been submitted!' + ); + }) + .catch((error) => { + console.error( + 'There was an error submitting the feedback', + error + ); + }); + } else { + // User is not logged in or user details are not available + console.error( + 'User is not authenticated or user details are missing.' + ); + } + }} + > + {({ isSubmitting, setFieldValue }) => ( +
+ +
+ { + setRating(value); + setFieldValue('rating', value); + }} + onPointerMove={onPointerMove} + initialValue={rating} + /> +
+
+ + +
+ + )} +
+ +
+ ); +} + +EmailFeedback.propTypes = { + handleFormSwitch: PropTypes.func.isRequired, + rating: PropTypes.number.isRequired, + onPointerMove: PropTypes.func.isRequired, + setRating: PropTypes.func.isRequired, + onClose: PropTypes.func.isRequired, +}; diff --git a/src/components/Feedback/Feedback.css b/src/components/Feedback/Feedback.css new file mode 100644 index 0000000..fd9fae5 --- /dev/null +++ b/src/components/Feedback/Feedback.css @@ -0,0 +1,69 @@ +/* MODAL Styling */ +.overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.7); +} + +.feedback-modal { + position: absolute; + top: 50%; + left: 50%; + width: 75%; + height: 75%; + transform: translate(-50%, -50%); + background: white; + padding: 20px; + border-radius: 10px; +} + +.close-button { + border: none; + font-weight: bold; + font-size: 1.5em; + cursor: pointer; + background: none; +} + +/* FORM Styling */ +.modal-form { + width: 100%; + height: 100%; + padding: 4%; +} + +.feedback-modal h2 { + text-align: center; + margin-bottom: 10%; +} + +.feedback-textarea { + width: 100%; + height: 100px; + margin-bottom: 10px; + padding: 10px; + border-radius: 5px; + border: 1px solid #ccc; + resize: none; +} + +.button-group { + display: flex; + justify-content: space-around; +} + +.feedback-textarea { + resize: none; +} + +.rating-container { + display: flex; + justify-content: center; + margin-bottom: 20px; +} + + + diff --git a/src/components/Feedback/InitialFeedbackPrompt.jsx b/src/components/Feedback/InitialFeedbackPrompt.jsx new file mode 100644 index 0000000..15d3162 --- /dev/null +++ b/src/components/Feedback/InitialFeedbackPrompt.jsx @@ -0,0 +1,284 @@ +// import React, { useState } from 'react'; +// import PropTypes from 'prop-types'; +// import Modal from 'react-modal'; +// import { Formik, Form, Field } from 'formik'; +// import * as Yup from 'yup'; +// import { Rating } from 'react-simple-star-rating'; +// import toast, { Toaster } from 'react-hot-toast'; +// import './Feedback.css'; +// import axios from 'axios'; + +// // Bind modal to app +// Modal.setAppElement('#root'); + +// // Validate direct feedback +// const feedbackValidationSchema = Yup.object().shape({ +// feedback: Yup.string() +// .trim() +// .required('Feedback is required') +// .max(2000, 'Feedback should not exceed 2000 characters'), +// rating: Yup.number() +// .required('Rating is required') +// .min(0, 'Rating should be at least 0') +// .max(5, 'Rating should not exceed 5'), +// }); + +// export function CaptureFeedback({ isActive, onClose }) { +// const [formToShow, setFormToShow] = useState('initial'); // State for which form to render +// const [rating, setRating] = useState(0); // State for feedback rating + +// // Switches form state +// const handleFormSwitch = (formType) => { +// setRating(0); +// setFormToShow(formType); +// }; + +// // Handle hover over rating component +// const onPointerMove = (value, index) => { +// setRating(value); +// }; + +// return ( +// <> +// +// {/* Modal close button */} +// +//
+//

+// We appreciate your feedback.

Please let us +// know how we can improve. +//

+// {/* INITIAL FORM */} +// {formToShow === 'initial' && ( +//
+// +// +//
+// )} +// {/* DIRECT FEEDBACK FORM */} +// {formToShow === 'feedback' && ( +// { +// //Get token and user info +// const token = localStorage.getItem('token'); +// const user = JSON.parse( +// localStorage.getItem('user') +// ); + +// if (token && user) { +// // User logged in and user details are available +// const userId = user._id; +// axios +// .post( +// 'http://localhost:3001/api/user/feedback/new', +// { +// feedback: values.feedback, +// rating: values.rating, +// author: userId, +// }, +// { +// headers: { +// 'Content-Type': +// 'application/json', +// Authorization: `Bearer ${token}`, +// }, +// } +// ) +// .then((response) => {}) +// .then(() => { +// setSubmitting(false); +// onClose(); +// toast.success( +// 'Thank you, your feedback has been submitted!' +// ); +// }) +// .catch((error) => { +// console.error( +// 'There was an error submitting the feedback', +// error +// ); +// }); +// } else { +// // User is not logged in or user details are not available +// console.error( +// 'User is not authenticated or user details are missing.' +// ); +// } +// }} +// > +// {({ isSubmitting, setFieldValue }) => ( +//
+// +//
+//
+// { +// setRating(value); +// setFieldValue( +// 'rating', +// value +// ); +// }} +// onPointerMove={onPointerMove} +// initialValue={rating} +// /> +//
+//
+//
+// +// +//
+// +// )} +//
+// )} +// {/* EMAIL FEEDBACK FORM */} +// {formToShow === 'email' && ( +// { +// const token = localStorage.getItem('token'); +// const user = JSON.parse( +// localStorage.getItem('user') +// ); +// const userId = user._id; +// const userEmail = user.email; +// const username = user.username; + +// if (token && user) { +// // User logged in and user details are available +// axios +// .post( +// 'http://localhost:3001/api/user/feedback/mail', +// { +// feedback: values.feedback, +// rating: values.rating, +// userId: userId, +// userEmail: userEmail, +// username: username, +// }, +// { +// headers: { +// 'Content-Type': +// 'application/json', +// Authorization: `Bearer ${token}`, +// }, +// } +// ) +// .then((response) => {}) +// .then(() => { +// setSubmitting(false); +// onClose(); +// toast.success( +// 'Thank you, your feedback has been submitted!' +// ); +// }) +// .catch((error) => { +// console.error( +// 'There was an error submitting the feedback', +// error +// ); +// }); +// } else { +// // User is not logged in or user details are not available +// console.error( +// 'User is not authenticated or user details are missing.' +// ); +// } +// }} +// > +// {({ isSubmitting, setFieldValue }) => ( +//
+// +//
+// { +// setRating(value); +// setFieldValue('rating', value); +// }} +// onPointerMove={onPointerMove} +// initialValue={rating} +// /> +//
+//
+// +// +//
+// +// )} +//
+// )} +//
+//
+// +// +// ); +// } + +// CaptureFeedback.propTypes = { +// isActive: PropTypes.bool.isRequired, +// onClose: PropTypes.func.isRequired, +// }; From 220284159f11caf4db611d49d1ab27d15bfd9fc9 Mon Sep 17 00:00:00 2001 From: Viktor Ristic Date: Sun, 10 Sep 2023 00:21:15 -0400 Subject: [PATCH 37/45] Update classNames for styling --- src/components/Feedback/CaptureFeedback.jsx | 70 ++- .../Feedback/InitialFeedbackPrompt.jsx | 550 +++++++++--------- 2 files changed, 313 insertions(+), 307 deletions(-) diff --git a/src/components/Feedback/CaptureFeedback.jsx b/src/components/Feedback/CaptureFeedback.jsx index 8420fac..01e1d94 100644 --- a/src/components/Feedback/CaptureFeedback.jsx +++ b/src/components/Feedback/CaptureFeedback.jsx @@ -26,42 +26,48 @@ export function CaptureFeedback({ isActive, onClose }) { isOpen={isActive} onRequestClose={onClose} contentLabel='Feedback Modal' + className='feedback-modal' + overlayClassName='overlay' > -

- We appreciate your feedback. Please let us know how we can - improve. -

- {formToShow === 'initial' && ( -
- - -
- )} - {formToShow === 'feedback' && ( - - )} - {formToShow === 'email' && ( - - )} +
+

+ We appreciate your feedback.
+ Please let us know how we can improve. +

+ {formToShow === 'initial' && ( +
+ + +
+ )} + {formToShow === 'feedback' && ( + + )} + {formToShow === 'email' && ( + + )} +
diff --git a/src/components/Feedback/InitialFeedbackPrompt.jsx b/src/components/Feedback/InitialFeedbackPrompt.jsx index 15d3162..1f338d0 100644 --- a/src/components/Feedback/InitialFeedbackPrompt.jsx +++ b/src/components/Feedback/InitialFeedbackPrompt.jsx @@ -1,284 +1,284 @@ -// import React, { useState } from 'react'; -// import PropTypes from 'prop-types'; -// import Modal from 'react-modal'; -// import { Formik, Form, Field } from 'formik'; -// import * as Yup from 'yup'; -// import { Rating } from 'react-simple-star-rating'; -// import toast, { Toaster } from 'react-hot-toast'; -// import './Feedback.css'; -// import axios from 'axios'; +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import Modal from 'react-modal'; +import { Formik, Form, Field } from 'formik'; +import * as Yup from 'yup'; +import { Rating } from 'react-simple-star-rating'; +import toast, { Toaster } from 'react-hot-toast'; +import './Feedback.css'; +import axios from 'axios'; -// // Bind modal to app -// Modal.setAppElement('#root'); +// Bind modal to app +Modal.setAppElement('#root'); -// // Validate direct feedback -// const feedbackValidationSchema = Yup.object().shape({ -// feedback: Yup.string() -// .trim() -// .required('Feedback is required') -// .max(2000, 'Feedback should not exceed 2000 characters'), -// rating: Yup.number() -// .required('Rating is required') -// .min(0, 'Rating should be at least 0') -// .max(5, 'Rating should not exceed 5'), -// }); +// Validate direct feedback +const feedbackValidationSchema = Yup.object().shape({ + feedback: Yup.string() + .trim() + .required('Feedback is required') + .max(2000, 'Feedback should not exceed 2000 characters'), + rating: Yup.number() + .required('Rating is required') + .min(0, 'Rating should be at least 0') + .max(5, 'Rating should not exceed 5'), +}); -// export function CaptureFeedback({ isActive, onClose }) { -// const [formToShow, setFormToShow] = useState('initial'); // State for which form to render -// const [rating, setRating] = useState(0); // State for feedback rating +export function CaptureFeedback({ isActive, onClose }) { + const [formToShow, setFormToShow] = useState('initial'); // State for which form to render + const [rating, setRating] = useState(0); // State for feedback rating -// // Switches form state -// const handleFormSwitch = (formType) => { -// setRating(0); -// setFormToShow(formType); -// }; + // Switches form state + const handleFormSwitch = (formType) => { + setRating(0); + setFormToShow(formType); + }; -// // Handle hover over rating component -// const onPointerMove = (value, index) => { -// setRating(value); -// }; + // Handle hover over rating component + const onPointerMove = (value, index) => { + setRating(value); + }; -// return ( -// <> -// -// {/* Modal close button */} -// -//
-//

-// We appreciate your feedback.

Please let us -// know how we can improve. -//

-// {/* INITIAL FORM */} -// {formToShow === 'initial' && ( -//
-// -// -//
-// )} -// {/* DIRECT FEEDBACK FORM */} -// {formToShow === 'feedback' && ( -// { -// //Get token and user info -// const token = localStorage.getItem('token'); -// const user = JSON.parse( -// localStorage.getItem('user') -// ); + return ( + <> + + {/* Modal close button */} + +
+

+ We appreciate your feedback.

Please let us + know how we can improve. +

+ {/* INITIAL FORM */} + {formToShow === 'initial' && ( +
+ + +
+ )} + {/* DIRECT FEEDBACK FORM */} + {formToShow === 'feedback' && ( + { + //Get token and user info + const token = localStorage.getItem('token'); + const user = JSON.parse( + localStorage.getItem('user') + ); -// if (token && user) { -// // User logged in and user details are available -// const userId = user._id; -// axios -// .post( -// 'http://localhost:3001/api/user/feedback/new', -// { -// feedback: values.feedback, -// rating: values.rating, -// author: userId, -// }, -// { -// headers: { -// 'Content-Type': -// 'application/json', -// Authorization: `Bearer ${token}`, -// }, -// } -// ) -// .then((response) => {}) -// .then(() => { -// setSubmitting(false); -// onClose(); -// toast.success( -// 'Thank you, your feedback has been submitted!' -// ); -// }) -// .catch((error) => { -// console.error( -// 'There was an error submitting the feedback', -// error -// ); -// }); -// } else { -// // User is not logged in or user details are not available -// console.error( -// 'User is not authenticated or user details are missing.' -// ); -// } -// }} -// > -// {({ isSubmitting, setFieldValue }) => ( -//
-// -//
-//
-// { -// setRating(value); -// setFieldValue( -// 'rating', -// value -// ); -// }} -// onPointerMove={onPointerMove} -// initialValue={rating} -// /> -//
-//
-//
-// -// -//
-// -// )} -//
-// )} -// {/* EMAIL FEEDBACK FORM */} -// {formToShow === 'email' && ( -// { -// const token = localStorage.getItem('token'); -// const user = JSON.parse( -// localStorage.getItem('user') -// ); -// const userId = user._id; -// const userEmail = user.email; -// const username = user.username; + if (token && user) { + // User logged in and user details are available + const userId = user._id; + axios + .post( + 'http://localhost:3001/api/user/feedback/new', + { + feedback: values.feedback, + rating: values.rating, + author: userId, + }, + { + headers: { + 'Content-Type': + 'application/json', + Authorization: `Bearer ${token}`, + }, + } + ) + .then((response) => {}) + .then(() => { + setSubmitting(false); + onClose(); + toast.success( + 'Thank you, your feedback has been submitted!' + ); + }) + .catch((error) => { + console.error( + 'There was an error submitting the feedback', + error + ); + }); + } else { + // User is not logged in or user details are not available + console.error( + 'User is not authenticated or user details are missing.' + ); + } + }} + > + {({ isSubmitting, setFieldValue }) => ( +
+ +
+
+ { + setRating(value); + setFieldValue( + 'rating', + value + ); + }} + onPointerMove={onPointerMove} + initialValue={rating} + /> +
+
+
+ + +
+ + )} +
+ )} + {/* EMAIL FEEDBACK FORM */} + {formToShow === 'email' && ( + { + const token = localStorage.getItem('token'); + const user = JSON.parse( + localStorage.getItem('user') + ); + const userId = user._id; + const userEmail = user.email; + const username = user.username; -// if (token && user) { -// // User logged in and user details are available -// axios -// .post( -// 'http://localhost:3001/api/user/feedback/mail', -// { -// feedback: values.feedback, -// rating: values.rating, -// userId: userId, -// userEmail: userEmail, -// username: username, -// }, -// { -// headers: { -// 'Content-Type': -// 'application/json', -// Authorization: `Bearer ${token}`, -// }, -// } -// ) -// .then((response) => {}) -// .then(() => { -// setSubmitting(false); -// onClose(); -// toast.success( -// 'Thank you, your feedback has been submitted!' -// ); -// }) -// .catch((error) => { -// console.error( -// 'There was an error submitting the feedback', -// error -// ); -// }); -// } else { -// // User is not logged in or user details are not available -// console.error( -// 'User is not authenticated or user details are missing.' -// ); -// } -// }} -// > -// {({ isSubmitting, setFieldValue }) => ( -//
-// -//
-// { -// setRating(value); -// setFieldValue('rating', value); -// }} -// onPointerMove={onPointerMove} -// initialValue={rating} -// /> -//
-//
-// -// -//
-// -// )} -//
-// )} -//
-//
-// -// -// ); -// } + if (token && user) { + // User logged in and user details are available + axios + .post( + 'http://localhost:3001/api/user/feedback/mail', + { + feedback: values.feedback, + rating: values.rating, + userId: userId, + userEmail: userEmail, + username: username, + }, + { + headers: { + 'Content-Type': + 'application/json', + Authorization: `Bearer ${token}`, + }, + } + ) + .then((response) => {}) + .then(() => { + setSubmitting(false); + onClose(); + toast.success( + 'Thank you, your feedback has been submitted!' + ); + }) + .catch((error) => { + console.error( + 'There was an error submitting the feedback', + error + ); + }); + } else { + // User is not logged in or user details are not available + console.error( + 'User is not authenticated or user details are missing.' + ); + } + }} + > + {({ isSubmitting, setFieldValue }) => ( +
+ +
+ { + setRating(value); + setFieldValue('rating', value); + }} + onPointerMove={onPointerMove} + initialValue={rating} + /> +
+
+ + +
+ + )} +
+ )} +
+
+ + + ); +} -// CaptureFeedback.propTypes = { -// isActive: PropTypes.bool.isRequired, -// onClose: PropTypes.func.isRequired, -// }; +CaptureFeedback.propTypes = { + isActive: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, +}; From ca9089b50055f7cd229eceb5a38f0cd6e0c620ef Mon Sep 17 00:00:00 2001 From: Viktor Ristic Date: Sun, 10 Sep 2023 22:42:15 -0400 Subject: [PATCH 38/45] Fix Rating hover bug after initial rating set --- src/components/Feedback/CaptureFeedback.jsx | 22 ++++++++++++++------- src/components/Feedback/EmailFeedback.jsx | 9 +++++++-- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/components/Feedback/CaptureFeedback.jsx b/src/components/Feedback/CaptureFeedback.jsx index 01e1d94..02b0225 100644 --- a/src/components/Feedback/CaptureFeedback.jsx +++ b/src/components/Feedback/CaptureFeedback.jsx @@ -8,7 +8,7 @@ import EmailFeedback from './EmailFeedback'; Modal.setAppElement('#root'); export function CaptureFeedback({ isActive, onClose }) { - const [formToShow, setFormToShow] = useState('initial'); + const [formToShow, setFormToShow] = useState('initialPrompt'); const [rating, setRating] = useState(0); const handleFormSwitch = (formType) => { @@ -17,7 +17,9 @@ export function CaptureFeedback({ isActive, onClose }) { }; const onPointerMove = (value) => { - setRating(value); + if (rating === 0) { + setRating(value); + } }; return ( @@ -37,19 +39,25 @@ export function CaptureFeedback({ isActive, onClose }) { We appreciate your feedback.
Please let us know how we can improve. - {formToShow === 'initial' && ( + {formToShow === 'initialPrompt' && (
-
)} - {formToShow === 'feedback' && ( + {formToShow === 'directFeedback' && ( )} - {formToShow === 'email' && ( + {formToShow === 'emailFeedback' && ( { + if (!isSubmitting) { + //IF statement should prevent rating state change when submitting but not working? + onPointerMove(value); + } + }} initialValue={rating} />
From 3428cd9574ad932f5408cb9b2d611cde93bd79ee Mon Sep 17 00:00:00 2001 From: Viktor Ristic Date: Sun, 10 Sep 2023 22:45:58 -0400 Subject: [PATCH 39/45] Remove redundant files/components --- .../CaptureFeedback/CaptureFeedback.css | 69 ----- .../CaptureFeedback/CaptureFeedback.jsx | 284 ------------------ .../Feedback/InitialFeedbackPrompt.jsx | 284 ------------------ 3 files changed, 637 deletions(-) delete mode 100644 src/components/CaptureFeedback/CaptureFeedback.css delete mode 100644 src/components/CaptureFeedback/CaptureFeedback.jsx delete mode 100644 src/components/Feedback/InitialFeedbackPrompt.jsx diff --git a/src/components/CaptureFeedback/CaptureFeedback.css b/src/components/CaptureFeedback/CaptureFeedback.css deleted file mode 100644 index fd9fae5..0000000 --- a/src/components/CaptureFeedback/CaptureFeedback.css +++ /dev/null @@ -1,69 +0,0 @@ -/* MODAL Styling */ -.overlay { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, 0.7); -} - -.feedback-modal { - position: absolute; - top: 50%; - left: 50%; - width: 75%; - height: 75%; - transform: translate(-50%, -50%); - background: white; - padding: 20px; - border-radius: 10px; -} - -.close-button { - border: none; - font-weight: bold; - font-size: 1.5em; - cursor: pointer; - background: none; -} - -/* FORM Styling */ -.modal-form { - width: 100%; - height: 100%; - padding: 4%; -} - -.feedback-modal h2 { - text-align: center; - margin-bottom: 10%; -} - -.feedback-textarea { - width: 100%; - height: 100px; - margin-bottom: 10px; - padding: 10px; - border-radius: 5px; - border: 1px solid #ccc; - resize: none; -} - -.button-group { - display: flex; - justify-content: space-around; -} - -.feedback-textarea { - resize: none; -} - -.rating-container { - display: flex; - justify-content: center; - margin-bottom: 20px; -} - - - diff --git a/src/components/CaptureFeedback/CaptureFeedback.jsx b/src/components/CaptureFeedback/CaptureFeedback.jsx deleted file mode 100644 index 52a6a17..0000000 --- a/src/components/CaptureFeedback/CaptureFeedback.jsx +++ /dev/null @@ -1,284 +0,0 @@ -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; -import Modal from 'react-modal'; -import { Formik, Form, Field } from 'formik'; -import * as Yup from 'yup'; -import { Rating } from 'react-simple-star-rating'; -import toast, { Toaster } from 'react-hot-toast'; -import './CaptureFeedback.css'; -import axios from 'axios'; - -// Bind modal to app -Modal.setAppElement('#root'); - -// Validate direct feedback -const feedbackValidationSchema = Yup.object().shape({ - feedback: Yup.string() - .trim() - .required('Feedback is required') - .max(2000, 'Feedback should not exceed 2000 characters'), - rating: Yup.number() - .required('Rating is required') - .min(0, 'Rating should be at least 0') - .max(5, 'Rating should not exceed 5'), -}); - -export function CaptureFeedback({ isActive, onClose }) { - const [formToShow, setFormToShow] = useState('initial'); // State for which form to render - const [rating, setRating] = useState(0); // State for feedback rating - - // Switches form state - const handleFormSwitch = (formType) => { - setRating(0); - setFormToShow(formType); - }; - - // Handle hover over rating component - const onPointerMove = (value, index) => { - setRating(value); - }; - - return ( - <> - - {/* Modal close button */} - -
-

- We appreciate your feedback.

Please let us - know how we can improve. -

- {/* INITIAL FORM */} - {formToShow === 'initial' && ( -
- - -
- )} - {/* DIRECT FEEDBACK FORM */} - {formToShow === 'feedback' && ( - { - //Get token and user info - const token = localStorage.getItem('token'); - const user = JSON.parse( - localStorage.getItem('user') - ); - - if (token && user) { - // User logged in and user details are available - const userId = user._id; - axios - .post( - 'http://localhost:3001/api/user/feedback/new', - { - feedback: values.feedback, - rating: values.rating, - author: userId, - }, - { - headers: { - 'Content-Type': - 'application/json', - Authorization: `Bearer ${token}`, - }, - } - ) - .then((response) => {}) - .then(() => { - setSubmitting(false); - onClose(); - toast.success( - 'Thank you, your feedback has been submitted!' - ); - }) - .catch((error) => { - console.error( - 'There was an error submitting the feedback', - error - ); - }); - } else { - // User is not logged in or user details are not available - console.error( - 'User is not authenticated or user details are missing.' - ); - } - }} - > - {({ isSubmitting, setFieldValue }) => ( -
- -
-
- { - setRating(value); - setFieldValue( - 'rating', - value - ); - }} - onPointerMove={onPointerMove} - initialValue={rating} - /> -
-
-
- - -
- - )} -
- )} - {/* EMAIL FEEDBACK FORM */} - {formToShow === 'email' && ( - { - const token = localStorage.getItem('token'); - const user = JSON.parse( - localStorage.getItem('user') - ); - const userId = user._id; - const userEmail = user.email; - const username = user.username; - - if (token && user) { - // User logged in and user details are available - axios - .post( - 'http://localhost:3001/api/user/feedback/mail', - { - feedback: values.feedback, - rating: values.rating, - userId: userId, - userEmail: userEmail, - username: username, - }, - { - headers: { - 'Content-Type': - 'application/json', - Authorization: `Bearer ${token}`, - }, - } - ) - .then((response) => {}) - .then(() => { - setSubmitting(false); - onClose(); - toast.success( - 'Thank you, your feedback has been submitted!' - ); - }) - .catch((error) => { - console.error( - 'There was an error submitting the feedback', - error - ); - }); - } else { - // User is not logged in or user details are not available - console.error( - 'User is not authenticated or user details are missing.' - ); - } - }} - > - {({ isSubmitting, setFieldValue }) => ( -
- -
- { - setRating(value); - setFieldValue('rating', value); - }} - onPointerMove={onPointerMove} - initialValue={rating} - /> -
-
- - -
- - )} -
- )} -
-
- - - ); -} - -CaptureFeedback.propTypes = { - isActive: PropTypes.bool.isRequired, - onClose: PropTypes.func.isRequired, -}; diff --git a/src/components/Feedback/InitialFeedbackPrompt.jsx b/src/components/Feedback/InitialFeedbackPrompt.jsx deleted file mode 100644 index 1f338d0..0000000 --- a/src/components/Feedback/InitialFeedbackPrompt.jsx +++ /dev/null @@ -1,284 +0,0 @@ -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; -import Modal from 'react-modal'; -import { Formik, Form, Field } from 'formik'; -import * as Yup from 'yup'; -import { Rating } from 'react-simple-star-rating'; -import toast, { Toaster } from 'react-hot-toast'; -import './Feedback.css'; -import axios from 'axios'; - -// Bind modal to app -Modal.setAppElement('#root'); - -// Validate direct feedback -const feedbackValidationSchema = Yup.object().shape({ - feedback: Yup.string() - .trim() - .required('Feedback is required') - .max(2000, 'Feedback should not exceed 2000 characters'), - rating: Yup.number() - .required('Rating is required') - .min(0, 'Rating should be at least 0') - .max(5, 'Rating should not exceed 5'), -}); - -export function CaptureFeedback({ isActive, onClose }) { - const [formToShow, setFormToShow] = useState('initial'); // State for which form to render - const [rating, setRating] = useState(0); // State for feedback rating - - // Switches form state - const handleFormSwitch = (formType) => { - setRating(0); - setFormToShow(formType); - }; - - // Handle hover over rating component - const onPointerMove = (value, index) => { - setRating(value); - }; - - return ( - <> - - {/* Modal close button */} - -
-

- We appreciate your feedback.

Please let us - know how we can improve. -

- {/* INITIAL FORM */} - {formToShow === 'initial' && ( -
- - -
- )} - {/* DIRECT FEEDBACK FORM */} - {formToShow === 'feedback' && ( - { - //Get token and user info - const token = localStorage.getItem('token'); - const user = JSON.parse( - localStorage.getItem('user') - ); - - if (token && user) { - // User logged in and user details are available - const userId = user._id; - axios - .post( - 'http://localhost:3001/api/user/feedback/new', - { - feedback: values.feedback, - rating: values.rating, - author: userId, - }, - { - headers: { - 'Content-Type': - 'application/json', - Authorization: `Bearer ${token}`, - }, - } - ) - .then((response) => {}) - .then(() => { - setSubmitting(false); - onClose(); - toast.success( - 'Thank you, your feedback has been submitted!' - ); - }) - .catch((error) => { - console.error( - 'There was an error submitting the feedback', - error - ); - }); - } else { - // User is not logged in or user details are not available - console.error( - 'User is not authenticated or user details are missing.' - ); - } - }} - > - {({ isSubmitting, setFieldValue }) => ( -
- -
-
- { - setRating(value); - setFieldValue( - 'rating', - value - ); - }} - onPointerMove={onPointerMove} - initialValue={rating} - /> -
-
-
- - -
- - )} -
- )} - {/* EMAIL FEEDBACK FORM */} - {formToShow === 'email' && ( - { - const token = localStorage.getItem('token'); - const user = JSON.parse( - localStorage.getItem('user') - ); - const userId = user._id; - const userEmail = user.email; - const username = user.username; - - if (token && user) { - // User logged in and user details are available - axios - .post( - 'http://localhost:3001/api/user/feedback/mail', - { - feedback: values.feedback, - rating: values.rating, - userId: userId, - userEmail: userEmail, - username: username, - }, - { - headers: { - 'Content-Type': - 'application/json', - Authorization: `Bearer ${token}`, - }, - } - ) - .then((response) => {}) - .then(() => { - setSubmitting(false); - onClose(); - toast.success( - 'Thank you, your feedback has been submitted!' - ); - }) - .catch((error) => { - console.error( - 'There was an error submitting the feedback', - error - ); - }); - } else { - // User is not logged in or user details are not available - console.error( - 'User is not authenticated or user details are missing.' - ); - } - }} - > - {({ isSubmitting, setFieldValue }) => ( -
- -
- { - setRating(value); - setFieldValue('rating', value); - }} - onPointerMove={onPointerMove} - initialValue={rating} - /> -
-
- - -
- - )} -
- )} -
-
- - - ); -} - -CaptureFeedback.propTypes = { - isActive: PropTypes.bool.isRequired, - onClose: PropTypes.func.isRequired, -}; From 79b13a50ec80d7d1d05ab760e7f04625d952e178 Mon Sep 17 00:00:00 2001 From: Viktor Ristic Date: Sun, 10 Sep 2023 23:45:01 -0400 Subject: [PATCH 40/45] Update 'Back' onClick to rerender initialPrompt --- src/components/Feedback/DirectFeedback.jsx | 4 +++- src/components/Feedback/EmailFeedback.jsx | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/Feedback/DirectFeedback.jsx b/src/components/Feedback/DirectFeedback.jsx index 3ac3657..4d4a000 100644 --- a/src/components/Feedback/DirectFeedback.jsx +++ b/src/components/Feedback/DirectFeedback.jsx @@ -100,7 +100,9 @@ export default function DirectFeedback({
-

+ We appreciate your feedback.
Please let us know how we can improve. -

+ {formToShow === 'initialPrompt' && (
Date: Mon, 11 Sep 2023 17:18:04 -0400 Subject: [PATCH 45/45] Update .variable.env example file --- src/server/.variable.env | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/server/.variable.env b/src/server/.variable.env index 0acd8ad..bd17856 100644 --- a/src/server/.variable.env +++ b/src/server/.variable.env @@ -1,2 +1,6 @@ MONGO_DB=mongodb://username:password@localhost:27017/codebooker PORT=3001 + +//Credentials for sending/receiving feedback from server +MAIL_USERNAME="senderAddressEmail" +MAIL_PASSWORD="senderAddressPassword" \ No newline at end of file