Add feedback form

This commit is contained in:
Jordan Eldredge 2021-10-13 22:09:57 -07:00
parent 55e14a0ab4
commit ee2ca7bef5
8 changed files with 197 additions and 7 deletions

View file

@ -2,6 +2,7 @@ import React, { useCallback } from "react";
import { connect } from "react-redux";
import Head from "./Head";
import About from "./About";
import Feedback from "./Feedback";
import Header from "./Header";
import Overlay from "./Overlay";
import SkinTable from "./SkinTable";
@ -27,10 +28,8 @@ const getTableDimensions = (windowWidth, scale) => {
function App(props) {
const scrollbarWidth = useScrollbarWidth();
const {
windowWidth: windowWidthWithScrollabar,
windowHeight,
} = useWindowSize();
const { windowWidth: windowWidthWithScrollabar, windowHeight } =
useWindowSize();
const { columnWidth, rowHeight, columnCount } = getTableDimensions(
windowWidthWithScrollabar - scrollbarWidth,
@ -75,7 +74,11 @@ function App(props) {
windowWidth={windowWidthWithScrollabar}
/>
)}
{props.page === ABOUT_PAGE ? (
{props.showFeedbackForm ? (
<Overlay>
<Feedback />
</Overlay>
) : props.page === ABOUT_PAGE ? (
<Overlay>
<About />
</Overlay>
@ -104,6 +107,7 @@ const mapStateToProps = (state) => ({
page: Selectors.getActiveContentPage(state),
scale: state.scale,
uploadViewOpen: Selectors.getUploadViewOpen(state),
showFeedbackForm: state.showFeedbackForm,
});
export default connect(mapStateToProps)(App);

87
src/Feedback.js Normal file
View file

@ -0,0 +1,87 @@
import React, { useState } from "react";
import { API_URL } from "./constants";
import { getUrl } from "./redux/selectors";
import * as Actions from "./redux/actionCreators";
import { useActionCreator } from "./hooks";
import { useCallback } from "react";
import { useSelector } from "react-redux";
export default function Feedback() {
const close = useActionCreator(Actions.closeFeedbackForm);
const [message, setMessage] = useState("");
const [email, setEmail] = useState("");
const [sending, setSending] = useState(false);
const [sent, setSent] = useState(false);
const url = useSelector(getUrl);
const send = useCallback(async () => {
const body = { message, email, url: "https://skins/webamp.org" + url };
setSending(true);
await fetch(`${API_URL}/feedback`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
setSent(true);
}, [message, email, url]);
if (sent) {
return (
<div className="static-content">
<h1>Sent!</h1>
<p>
Thanks for your feedback. I appreciate you taking the time to share
your thoughts.
</p>
<div style={{ height: 25, textAlign: "right" }}>
<button onClick={close} style={{ marginRight: 0 }}>
Close
</button>
</div>
</div>
);
}
return (
<div className="static-content">
<h1>Feedback</h1>
<p>
Let me know what you think about the Winamp Skin Museum. Bug reports,
feature suggestions, personal anecdotes, or criticism are all welcome.
</p>
<p>
<label>
Message
<textarea
disabled={sending}
value={message}
onChange={(e) => setMessage(e.target.value)}
style={{ display: "block", width: "100%", minHeight: 150 }}
placeholder="Your thoughts here..."
/>
</label>
</p>
<p>
<label>
Email (optional)
<input
disabled={sending}
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="user@example.com"
style={{ width: "100%" }}
/>
</label>
</p>
<p>
<div style={{ height: 25, textAlign: "right" }}>
<button onClick={send} style={{ marginRight: 0 }} disabled={sending}>
{sending ? "Sending..." : "Send"}
</button>
</div>
</p>
</div>
);
}

View file

@ -7,6 +7,9 @@ import { useActionCreator, useWindowSize } from "./hooks";
import { ReactComponent as AlgoliaLogo } from "./searchByAlgoliaDarkbBackground.svg";
import algoliaLogoSmallUrl from "./searchByAlgoliaSmall.png";
import UploadButton from "./UploadButton";
import FeedbackIcon from "./components/icons/FeedbackIcon";
import AboutIcon from "./components/icons/AboutIcon";
import RandomIcon from "./components/icons/RandomIcon";
function SearchLogo() {
const { windowWidth } = useWindowSize();
@ -53,6 +56,7 @@ function Header() {
const uploadViewOpen = useSelector(Selectors.getUploadViewOpen);
const setSearchQuery = useActionCreator(Actions.searchQueryChanged);
const showFeedbackForm = useActionCreator(Actions.showFeedbackForm);
const requestRandomSkin = useActionCreator(Actions.requestedRandomSkin);
const requestedAboutPage = useActionCreator(Actions.requestedAboutPage);
// const setScale = useActionCreator((scale) => ({ type: "SET_SCALE", scale }));
@ -117,16 +121,36 @@ function Header() {
onClick={() => {
requestRandomSkin();
}}
style={{
paddingLeft: "0.2rem",
paddingRight: "0.2rem",
}}
>
Random
<RandomIcon />
</button>
<button
title="Feedback"
onClick={() => {
showFeedbackForm();
}}
style={{
paddingLeft: "0.2rem",
paddingRight: "0.2rem",
}}
>
<FeedbackIcon />
</button>
<button
title="About"
onClick={() => {
requestedAboutPage();
}}
style={{
paddingLeft: "0.2rem",
paddingRight: "0.2rem",
}}
>
?
<AboutIcon />
</button>
</>
)}

View file

@ -0,0 +1,19 @@
import React from "react";
function AboutIcon(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
height="24px"
viewBox="0 0 24 24"
width="24px"
fill="#000000"
{...props}
>
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M11 7h2v2h-2zm0 4h2v6h-2zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z" />
</svg>
);
}
export default AboutIcon;

View file

@ -0,0 +1,17 @@
import React from "react";
function FeedbackIcon(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
height="24px"
viewBox="0 0 24 24"
{...props}
>
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M4 4h16v12H5.17L4 17.17V4m0-2c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2H4zm2 10h12v2H6v-2zm0-3h12v2H6V9zm0-3h12v2H6V6z" />
</svg>
);
}
export default FeedbackIcon;

View file

@ -0,0 +1,19 @@
import React from "react";
function RandomIcon(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
height="24px"
viewBox="0 0 24 24"
width="24px"
fill="#000000"
{...props}
>
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M10.59 9.17L5.41 4 4 5.41l5.17 5.17 1.42-1.41zM14.5 4l2.04 2.04L4 18.59 5.41 20 17.96 7.46 20 9.5V4h-5.5zm.33 9.41l-1.41 1.41 3.13 3.13L14.5 20H20v-5.5l-2.04 2.04-3.13-3.13z" />
</svg>
);
}
export default RandomIcon;

View file

@ -3,6 +3,14 @@ export function closeModal() {
return { type: "CLOSE_MODAL" };
}
export function closeFeedbackForm() {
return { type: "CLOSE_FEEDBACK_FORM" };
}
export function showFeedbackForm() {
return { type: "SHOW_FEEDBACK_FORM" };
}
export function searchQueryChanged(query) {
return { type: "SEARCH_QUERY_CHANGED", query };
}

View file

@ -18,6 +18,7 @@ const defaultState = {
skins: {},
showNsfw: false,
fileUploads: {},
showFeedbackForm: false,
};
function setUploadFileStatus(state, id, status, invalid) {
@ -204,6 +205,7 @@ export default function reducer(state = defaultState, action) {
activeContentPage: null,
focusedSkinFile: null,
fileExplorerOpen: false,
showFeedbackForm: false,
};
case "SEARCH_QUERY_CHANGED":
return {
@ -274,6 +276,16 @@ export default function reducer(state = defaultState, action) {
...state,
fileExplorerOpen: false,
};
case "SHOW_FEEDBACK_FORM":
return {
...state,
showFeedbackForm: true,
};
case "CLOSE_FEEDBACK_FORM":
return {
...state,
showFeedbackForm: false,
};
default:
return state;
}