mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-23 10:07:35 +00:00
Add feedback form
This commit is contained in:
parent
55e14a0ab4
commit
ee2ca7bef5
8 changed files with 197 additions and 7 deletions
14
src/App.js
14
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 ? (
|
||||
<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
87
src/Feedback.js
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
19
src/components/icons/AboutIcon.js
Normal file
19
src/components/icons/AboutIcon.js
Normal 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;
|
||||
17
src/components/icons/FeedbackIcon.js
Normal file
17
src/components/icons/FeedbackIcon.js
Normal 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;
|
||||
19
src/components/icons/RandomIcon.js
Normal file
19
src/components/icons/RandomIcon.js
Normal 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;
|
||||
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue