Skip to content

Commit

Permalink
#127 Implement feedback form
Browse files Browse the repository at this point in the history
Closes #127.
  • Loading branch information
blms committed Jan 7, 2021
1 parent 8761719 commit 6e78c04
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 1 deletion.
3 changes: 2 additions & 1 deletion .env.local.sample
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ AUTH_SECRET=random-string
DB_NAME=as4
EMAIL_SERVER_USER=example
EMAIL_SERVER_PASSWORD=password
EMAIL_SERVER_HOST=smpt.example.net
EMAIL_SERVER_HOST=smtp.example.net
EMAIL_SERVER_PORT=587
[email protected]
[email protected]
MONGODB_URI=mongodb://localhost:27017/as4
NEXTAUTH_URL=http://localhost:3000
NEXT_PUBLIC_SIGNING_URL=CLOUDFRONT_URL
Expand Down
117 changes: 117 additions & 0 deletions src/components/FeedbackButton/FeedbackButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import React, { useState } from 'react';
import {
Button,
Form,
Modal,
Nav,
} from 'react-bootstrap';
import { Formik } from 'formik';
import unfetch from 'unfetch';

const sendEmail = async (user, text) => {
const url = '/api/feedback';
const res = await unfetch(url, {
method: 'POST',
body: JSON.stringify({ name: user.name, text }),
headers: {
'Content-Type': 'application/json',
},
});
if (res.status === 200) {
const result = await res.json();
return Promise.resolve(result);
} return Promise.reject(Error(`Unable to send email: error ${res.status} received from server`));
};

const FeedbackModal = ({ show, setShow, session }) => (
<Formik
key="feedback-form"
initialValues={{ feedback: '' }}
onSubmit={async (values, actions) => {
await sendEmail(session.user, values.feedback)
.catch((error) => {
setShow(false);
actions.setSubmitting(false);
throw new Error('SEND_FEEDBACK_EMAIL_ERROR', error);
});
setShow(false);
actions.setSubmitting(false);
}}
>
{(props) => (
<Form noValidate onSubmit={props.handleSubmit}>
<Modal
show={show}
onHide={() => {
setShow(false);
}}
size="lg"
aria-labelledby="feedback-modal-title"
centered
>
<Modal.Header closeButton>
<Modal.Title id="feedback-modal-title">
Submit Feedback
</Modal.Title>
</Modal.Header>
<Modal.Body>
<h5>Thank you for testing out this early version of Annotation Studio 4.</h5>
<Form.Group controlId="feedback-textarea">
<Form.Label>
Here you may submit feedback directly
to the Annotation Studio team.
</Form.Label>
<Form.Control
as="textarea"
rows={3}
onChange={props.handleChange}
onBlur={props.handleBlur}
value={props.values.feedback}
name="feedback"
placeholder="Write feedback"
/>
</Form.Group>
{props.errors.feedback && <div id="feedback">{props.errors.feedback}</div>}
</Modal.Body>
<Modal.Footer style={{ display: 'flex', justifyContent: 'space-between' }}>
<Button variant="outline-secondary" onClick={() => { setShow(false); }}>Cancel</Button>
<Button
variant="success"
type="submit"
onClick={props.handleSubmit}
disabled={props.isSubmitting || props.errors.feedback || props.values.feedback === ''}
>
Submit
</Button>
</Modal.Footer>
</Modal>
</Form>
)}
</Formik>
);

const FeedbackButton = ({ session }) => {
const [modalShow, setModalShow] = useState(false);
return (
<>
{session && (
<>
<Nav.Link
onClick={() => setModalShow(true)}
style={{ color: '#f5c83a', textDecoration: 'none' }}
>
Feedback
</Nav.Link>

<FeedbackModal
show={modalShow}
setShow={setModalShow}
session={session}
/>
</>
)}
</>
);
};

export default FeedbackButton;
3 changes: 3 additions & 0 deletions src/components/FeedbackButton/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import FeedbackButton from './FeedbackButton';

export default FeedbackButton;
2 changes: 2 additions & 0 deletions src/components/Header/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
BoxArrowUpRight, BoxArrowInRight, BoxArrowRight, GearWideConnected,
} from 'react-bootstrap-icons';
import SecondNavbar from '../SecondNavbar';
import FeedbackButton from '../FeedbackButton';

function getEditProfileUrl(email) {
const slug = email.replace(/[*+~.()'"!:@]/g, '-');
Expand All @@ -33,6 +34,7 @@ function Header({
<Navbar.Toggle aria-controls="responsive-navbar-nav" />
<Navbar.Collapse className="justify-content-end">
<Nav>
<FeedbackButton session={session} />
<NavDropdown title="About" id="basic-nav-dropdown" data-testid="nav-about-dropdown">
<NavDropdown.Item href="https://www.annotationstudio.org/project/">Project</NavDropdown.Item>
<NavDropdown.Item href="https://www.annotationstudio.org/pedagogy/">Pedagogy</NavDropdown.Item>
Expand Down
28 changes: 28 additions & 0 deletions src/pages/api/feedback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import jwt from 'next-auth/jwt';
import sgMail from '@sendgrid/mail';

const secret = process.env.AUTH_SECRET;

const handler = async (req, res) => {
const { method } = req;
if (method === 'POST') {
const token = await jwt.getToken({ req, secret });
if (token && token.exp > 0) {
if (req.body.name && req.body.text) {
sgMail.setApiKey(process.env.EMAIL_SERVER_PASSWORD);
await sgMail
.send({
to: process.env.SUPPORT_EMAIL,
from: process.env.EMAIL_FROM,
subject: `[${process.env.SITE_NAME}] Feedback submission`,
text: `${req.body.name} writes: ${req.body.text}`,
html: `<h5>${req.body.name} writes:</h5> <p>${req.body.text}</p>`,
})
.catch((error) => res.status(500).end(`Error sending feedback: ${error.message}`));
res.status(200).json({ feedback: req.body.text });
} else res.status(400).end('Bad request');
} else res.status(403).end('Invalid or expired token');
} else res.status(405).end(`Method ${method} Not Allowed`);
};

export default handler;

0 comments on commit 6e78c04

Please sign in to comment.