Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
1640af2
Setup CaptureFeedback component and styling files
vktr-r2 Aug 30, 2023
6fe0a33
Add state management to component
vktr-r2 Aug 30, 2023
c5dc75e
Add styling to CaptureFeedback panel
vktr-r2 Aug 30, 2023
7cb12b8
Update panel width
vktr-r2 Aug 31, 2023
ca1224c
Update CaptureFeedback state management and pass props
vktr-r2 Aug 31, 2023
b1bceaf
Update styling
vktr-r2 Aug 31, 2023
6a82a0d
Convert panel to modal
vktr-r2 Sep 1, 2023
048c51c
Add Formik form to modal
vktr-r2 Sep 1, 2023
f5c860b
Update modal to prompt user with two feedback flows
vktr-r2 Sep 1, 2023
f61c78b
Add star rating functionality
vktr-r2 Sep 1, 2023
6ce5647
Update forms styling
vktr-r2 Sep 1, 2023
0f43ca3
Add comments
vktr-r2 Sep 1, 2023
7deb718
Add fetch POST to onSubmit function
vktr-r2 Sep 6, 2023
28795d9
Add handle response and errorpromise chain
vktr-r2 Sep 6, 2023
7a9df00
Update Yup direct feedback validation
vktr-r2 Sep 6, 2023
ef858fc
Add error handling for response
vktr-r2 Sep 6, 2023
41904f9
Update inital values for feedback rating
vktr-r2 Sep 6, 2023
bde4e27
Add login session validation to userRoutes
vktr-r2 Sep 6, 2023
14f928e
Update feedbackController to pull userId from current session
vktr-r2 Sep 6, 2023
2330edc
Add code to save userId in session for feedback submission
vktr-r2 Sep 6, 2023
d91be93
Update form validation to allow for initial rating
vktr-r2 Sep 6, 2023
af2aa2b
Add proxy
vktr-r2 Sep 7, 2023
dbd8cc2
Remove session authentication check
vktr-r2 Sep 7, 2023
0fee8d5
Refactor addfeedback func to receive author from req.body
vktr-r2 Sep 7, 2023
233dd8b
Refactor onSubmit to obtain user/token from localStorage
vktr-r2 Sep 7, 2023
897c8c6
Add setFieldValue to Rating component to update state
vktr-r2 Sep 7, 2023
fc1f05d
Add feedback/mail route
vktr-r2 Sep 8, 2023
96c167c
Implement emailFeedback controller function
vktr-r2 Sep 8, 2023
1befb0a
Update state management for Rating component
vktr-r2 Sep 8, 2023
ef5b2f0
Implement env vars
vktr-r2 Sep 8, 2023
e829c69
Implement email form onSubmit functionality
vktr-r2 Sep 8, 2023
31985f6
Update user data that is sent via axios POST request
vktr-r2 Sep 8, 2023
d374ff5
Rename field so input is included in email
vktr-r2 Sep 8, 2023
525a130
Refactor modal close flow after successful submission
vktr-r2 Sep 8, 2023
d5c6eb6
Add successful message using toast lib
vktr-r2 Sep 9, 2023
cc5ebd4
Modularize feedback code
vktr-r2 Sep 10, 2023
2202841
Update classNames for styling
vktr-r2 Sep 10, 2023
ca9089b
Fix Rating hover bug after initial rating set
vktr-r2 Sep 11, 2023
3428cd9
Remove redundant files/components
vktr-r2 Sep 11, 2023
79b13a5
Update 'Back' onClick to rerender initialPrompt
vktr-r2 Sep 11, 2023
499143a
Refactor modal header to use ypography
vktr-r2 Sep 11, 2023
1d73e5e
Adjust modal size
vktr-r2 Sep 11, 2023
888a790
Update spacing and button styling
vktr-r2 Sep 11, 2023
7c86c16
Update styling
vktr-r2 Sep 11, 2023
1d49f66
Update .variable.env example file
vktr-r2 Sep 11, 2023
d4ff474
Merge remote-tracking branch 'upstream/master'
vktr-r2 Sep 11, 2023
3953014
Merge updated main into feature/capture-feedback branch
vktr-r2 Sep 11, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,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",
Expand Down
21 changes: 17 additions & 4 deletions src/components/Dropdown/Dropdown.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -16,11 +16,12 @@ import HelpIcon from '@mui/icons-material/Help';
import MessageIcon from '@mui/icons-material/Message';
import PolicyIcon from '@mui/icons-material/Policy';
import ManageAccountsIcon from '@mui/icons-material/ManageAccounts';
import { useEffect } from 'react';
import { CaptureFeedback } from '../Feedback/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 modal
const open = Boolean(anchorEl);
const navigate = useNavigate();

Expand Down Expand Up @@ -62,6 +63,12 @@ export default function Dropdown() {
navigate('/privacy-policy');
}

// Toggle CaptureFeedback modal component visibility
function toggleFeedbackModal() {
setFeedbackVisible((prevFeedbackVisible) => !prevFeedbackVisible);
handleClose();
}

return (
<React.Fragment>
<Box
Expand All @@ -88,7 +95,6 @@ export default function Dropdown() {
id='account-menu'
open={open}
onClose={handleClose}
onClick={handleClose}
PaperProps={{
elevation: 0,
sx: {
Expand Down Expand Up @@ -122,7 +128,7 @@ export default function Dropdown() {
<HelpIcon fontSize='large' />
&nbsp; Help
</MenuItem>
<MenuItem>
<MenuItem onClick={toggleFeedbackModal}>
<MessageIcon fontSize='large' />
&nbsp; Feedback
</MenuItem>
Expand Down Expand Up @@ -162,6 +168,13 @@ export default function Dropdown() {
)}
</Menu>
<Toaster />
{/* Conditionally render CaptureFeedback modal component */}
{feedbackVisible && (
<CaptureFeedback
isActive={feedbackVisible}
onClose={toggleFeedbackModal}
/>
)}
</React.Fragment>
);
}
102 changes: 102 additions & 0 deletions src/components/Feedback/CaptureFeedback.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
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';
import Typography from '@mui/material/Typography';

Modal.setAppElement('#root');

export function CaptureFeedback({ isActive, onClose }) {
const [formToShow, setFormToShow] = useState('initialPrompt');
const [rating, setRating] = useState(0);

const handleFormSwitch = (formType) => {
setRating(0);
setFormToShow(formType);
};

const onPointerMove = (value) => {
if (rating === 0) {
setRating(value);
}
};

return (
<React.Fragment>
<Modal
isOpen={isActive}
onRequestClose={onClose}
contentLabel='Feedback Modal'
className='feedback-modal'
overlayClassName='overlay'
>
<button className='close-button' onClick={onClose}>
X
</button>
<div className='modal-form'>
<Typography
variant={'p'}
component='div'
className='feedback-header'
sx={{
flexGrow: 1,
letterSpacing: '0.009em',
marginTop: '1%',
marginBottom: '6%',
fontWeight: 'bold',
}}
>
We appreciate your feedback. <br />
Please let us know how we can improve.
</Typography>
{formToShow === 'initialPrompt' && (
<div className='button-group'>
<button
className='feedback-button'
onClick={() =>
handleFormSwitch('directFeedback')
}
>
Leave us a message
</button>
<button
className='feedback-button'
onClick={() =>
handleFormSwitch('emailFeedback')
}
>
Send us an email
</button>
</div>
)}
{formToShow === 'directFeedback' && (
<DirectFeedback
handleFormSwitch={handleFormSwitch}
rating={rating}
onPointerMove={onPointerMove}
setRating={setRating}
onClose={onClose}
/>
)}
{formToShow === 'emailFeedback' && (
<EmailFeedback
handleFormSwitch={handleFormSwitch}
rating={rating}
onPointerMove={onPointerMove}
setRating={setRating}
onClose={onClose}
/>
)}
</div>
</Modal>
<Toaster />
</React.Fragment>
);
}

CaptureFeedback.propTypes = {
isActive: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
};
132 changes: 132 additions & 0 deletions src/components/Feedback/DirectFeedback.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
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 (
<React.Fragment>
<Formik
initialValues={{ feedback: '', rating: rating }}
validationSchema={feedbackValidationSchema}
onSubmit={(values, { setSubmitting }) => {
//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 }) => (
<Form>
<Field
as='textarea'
name='feedback'
disabled={isSubmitting}
className='feedback-textarea'
/>
<div className='rating-container'>
<div className='rating-container'>
<Rating
disabled={isSubmitting}
onClick={(value) => {
setRating(value);
setFieldValue('rating', value);
}}
onPointerMove={onPointerMove}
initialValue={rating}
/>
</div>
</div>
<div className='button-group'>
<button
type='button'
disabled={isSubmitting}
onClick={() =>
handleFormSwitch('initialPrompt')
}
className='back-button'
>
Back
</button>
<button
type='submit'
disabled={isSubmitting}
className='submit-button'
>
Submit Feedback
</button>
</div>
</Form>
)}
</Formik>
<Toaster />
</React.Fragment>
);
}

DirectFeedback.propTypes = {
handleFormSwitch: PropTypes.func.isRequired,
rating: PropTypes.number.isRequired,
onPointerMove: PropTypes.func.isRequired,
setRating: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
};
Loading