diff --git a/src/App.js b/src/App.js
index a37e170a..36bc76cc 100644
--- a/src/App.js
+++ b/src/App.js
@@ -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 ? (
+
+
+
+ ) : props.page === ABOUT_PAGE ? (
@@ -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);
diff --git a/src/Feedback.js b/src/Feedback.js
new file mode 100644
index 00000000..8badbc75
--- /dev/null
+++ b/src/Feedback.js
@@ -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 (
+
+
Sent!
+
+ Thanks for your feedback. I appreciate you taking the time to share
+ your thoughts.
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
Feedback
+
+ Let me know what you think about the Winamp Skin Museum. Bug reports,
+ feature suggestions, personal anecdotes, or criticism are all welcome.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/Header.js b/src/Header.js
index d478f0ba..e6491c4f 100644
--- a/src/Header.js
+++ b/src/Header.js
@@ -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
+
+
+
>
)}
diff --git a/src/components/icons/AboutIcon.js b/src/components/icons/AboutIcon.js
new file mode 100644
index 00000000..80eec1ea
--- /dev/null
+++ b/src/components/icons/AboutIcon.js
@@ -0,0 +1,19 @@
+import React from "react";
+
+function AboutIcon(props) {
+ return (
+
+ );
+}
+
+export default AboutIcon;
diff --git a/src/components/icons/FeedbackIcon.js b/src/components/icons/FeedbackIcon.js
new file mode 100644
index 00000000..6053bc39
--- /dev/null
+++ b/src/components/icons/FeedbackIcon.js
@@ -0,0 +1,17 @@
+import React from "react";
+
+function FeedbackIcon(props) {
+ return (
+
+ );
+}
+
+export default FeedbackIcon;
diff --git a/src/components/icons/RandomIcon.js b/src/components/icons/RandomIcon.js
new file mode 100644
index 00000000..a2f4131c
--- /dev/null
+++ b/src/components/icons/RandomIcon.js
@@ -0,0 +1,19 @@
+import React from "react";
+
+function RandomIcon(props) {
+ return (
+
+ );
+}
+
+export default RandomIcon;
diff --git a/src/redux/actionCreators.js b/src/redux/actionCreators.js
index f2975e5e..7f9a2b72 100644
--- a/src/redux/actionCreators.js
+++ b/src/redux/actionCreators.js
@@ -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 };
}
diff --git a/src/redux/reducer.js b/src/redux/reducer.js
index ec1ce8d1..f751bca8 100644
--- a/src/redux/reducer.js
+++ b/src/redux/reducer.js
@@ -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;
}