Moved donate modal to its own route
Changed routes to be kebab case instead of camel case
This commit is contained in:
parent
becb2bfaad
commit
5b9cabf532
1
.env
1
.env
@ -1,3 +1,4 @@
|
|||||||
REACT_APP_BROKER_URL=http://localhost:9000
|
REACT_APP_BROKER_URL=http://localhost:9000
|
||||||
REACT_APP_ICE_SERVERS_URL=http://localhost:9000/iceservers
|
REACT_APP_ICE_SERVERS_URL=http://localhost:9000/iceservers
|
||||||
|
REACT_APP_STRIPE_API_KEY=pk_test_8M3NHrF1eI2b84ubF4F8rSTe0095R3f0My
|
||||||
REACT_APP_VERSION=$npm_package_version
|
REACT_APP_VERSION=$npm_package_version
|
@ -1,3 +1,4 @@
|
|||||||
REACT_APP_BROKER_URL=https://test.owlbear.rodeo
|
REACT_APP_BROKER_URL=https://test.owlbear.rodeo
|
||||||
REACT_APP_ICE_SERVERS_URL=https://test.owlbear.rodeo/iceservers
|
REACT_APP_ICE_SERVERS_URL=https://test.owlbear.rodeo/iceservers
|
||||||
|
REACT_APP_STRIPE_API_KEY=pk_live_MJjzi5djj524Y7h3fL5PNh4e00a852XD51
|
||||||
REACT_APP_VERSION=$npm_package_version
|
REACT_APP_VERSION=$npm_package_version
|
@ -9,6 +9,7 @@ import About from "./routes/About";
|
|||||||
import FAQ from "./routes/FAQ";
|
import FAQ from "./routes/FAQ";
|
||||||
import ReleaseNotes from "./routes/ReleaseNotes";
|
import ReleaseNotes from "./routes/ReleaseNotes";
|
||||||
import HowTo from "./routes/HowTo";
|
import HowTo from "./routes/HowTo";
|
||||||
|
import Donate from "./routes/Donate";
|
||||||
|
|
||||||
import { AuthProvider } from "./contexts/AuthContext";
|
import { AuthProvider } from "./contexts/AuthContext";
|
||||||
import { DatabaseProvider } from "./contexts/DatabaseContext";
|
import { DatabaseProvider } from "./contexts/DatabaseContext";
|
||||||
@ -27,10 +28,14 @@ function App() {
|
|||||||
<KeyboardProvider>
|
<KeyboardProvider>
|
||||||
<Router>
|
<Router>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/howTo">
|
<Route path="/donate">
|
||||||
|
<Donate />
|
||||||
|
</Route>
|
||||||
|
{/* Legacy support camel case routes */}
|
||||||
|
<Route path={["/howTo", "/how-to"]}>
|
||||||
<HowTo />
|
<HowTo />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/releaseNotes">
|
<Route path={["/releaseNotes", "/release-notes"]}>
|
||||||
<ReleaseNotes />
|
<ReleaseNotes />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/about">
|
<Route path="/about">
|
||||||
|
@ -24,10 +24,10 @@ function Footer() {
|
|||||||
<Link m={2} to="/faq" variant="footer">
|
<Link m={2} to="/faq" variant="footer">
|
||||||
FAQ
|
FAQ
|
||||||
</Link>
|
</Link>
|
||||||
<Link m={2} to="/releaseNotes" variant="footer">
|
<Link m={2} to="/release-notes" variant="footer">
|
||||||
Release Notes
|
Release Notes
|
||||||
</Link>
|
</Link>
|
||||||
<Link m={2} to="/howTo" variant="footer">
|
<Link m={2} to="/how-to" variant="footer">
|
||||||
How To
|
How To
|
||||||
</Link>
|
</Link>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
19
src/icons/DonateIcon.js
Normal file
19
src/icons/DonateIcon.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function DonateIcon() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentcolor"
|
||||||
|
style={{ margin: "0 4px" }}
|
||||||
|
>
|
||||||
|
<path d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M13.35 20.13c-.76.69-1.93.69-2.69-.01l-.11-.1C5.3 15.27 1.87 12.16 2 8.28c.06-1.7.93-3.33 2.34-4.29 2.64-1.8 5.9-.96 7.66 1.1 1.76-2.06 5.02-2.91 7.66-1.1 1.41.96 2.28 2.59 2.34 4.29.14 3.88-3.3 6.99-8.55 11.76l-.1.09z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DonateIcon;
|
@ -1,134 +0,0 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
|
||||||
import { Box, Label, Button, Flex, Radio, Text } from "theme-ui";
|
|
||||||
import { useLocation, useHistory } from "react-router-dom";
|
|
||||||
|
|
||||||
import Modal from "../components/Modal";
|
|
||||||
import LoadingOverlay from "../components/LoadingOverlay";
|
|
||||||
import Banner from "../components/Banner";
|
|
||||||
|
|
||||||
const skus = [
|
|
||||||
{ sku: "sku_H6DhHS1MimRPR9", price: "$5.00 AUD", name: "Small" },
|
|
||||||
{ sku: "sku_H6DhiQfHUkYUKd", price: "$15.00 AUD", name: "Medium" },
|
|
||||||
{ sku: "sku_H6DhbO2oUn9Sda", price: "$30.00 AUD", name: "Large" },
|
|
||||||
];
|
|
||||||
|
|
||||||
function DonationModal({ isOpen, onRequestClose }) {
|
|
||||||
// Handle callback from stripe
|
|
||||||
const location = useLocation();
|
|
||||||
const history = useHistory();
|
|
||||||
const query = new URLSearchParams(location.search);
|
|
||||||
const hasDonated = query.has("donated");
|
|
||||||
const showDonationForm = isOpen || query.get("donated") === "false";
|
|
||||||
|
|
||||||
const [loading, setLoading] = useState(showDonationForm);
|
|
||||||
const [error, setError] = useState(null);
|
|
||||||
|
|
||||||
const [stripe, setStripe] = useState();
|
|
||||||
useEffect(() => {
|
|
||||||
if (showDonationForm) {
|
|
||||||
import("@stripe/stripe-js").then(({ loadStripe }) => {
|
|
||||||
loadStripe("pk_live_MJjzi5djj524Y7h3fL5PNh4e00a852XD51")
|
|
||||||
.then((stripe) => {
|
|
||||||
setStripe(stripe);
|
|
||||||
setLoading(false);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
setError(err.message);
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [showDonationForm]);
|
|
||||||
|
|
||||||
function handleClose() {
|
|
||||||
if (hasDonated) {
|
|
||||||
history.push(location.pathname);
|
|
||||||
}
|
|
||||||
onRequestClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSubmit(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
if (!stripe) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setLoading(true);
|
|
||||||
stripe
|
|
||||||
.redirectToCheckout({
|
|
||||||
items: [{ sku: selectedSku, quantity: 1 }],
|
|
||||||
successUrl: `${window.location.href}?donated=true`,
|
|
||||||
cancelUrl: `${window.location.href}?donated=false`,
|
|
||||||
submitType: "donate",
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
setLoading(false);
|
|
||||||
if (response.error) {
|
|
||||||
setError(response.error.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const [selectedSku, setSelectedSku] = useState("sku_H6DhiQfHUkYUKd");
|
|
||||||
function handleSkuChange(event) {
|
|
||||||
setSelectedSku(event.target.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
const donationSuccessful = (
|
|
||||||
<Box>
|
|
||||||
<Text my={2} variant="heading" as="h1" sx={{ fontSize: 3 }}>
|
|
||||||
Thanks for donating! ʕ•ᴥ•ʔ
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
|
|
||||||
const donationForm = (
|
|
||||||
<Box as="form" onSubmit={handleSubmit}>
|
|
||||||
<Label py={2}>Support us with a donation</Label>
|
|
||||||
<Text as="p" mb={2} variant="caption">
|
|
||||||
One time donation
|
|
||||||
</Text>
|
|
||||||
{skus.map((sku) => (
|
|
||||||
<Label key={sku.sku}>
|
|
||||||
<Radio
|
|
||||||
name="donation"
|
|
||||||
checked={selectedSku === sku.sku}
|
|
||||||
value={sku.sku}
|
|
||||||
onChange={handleSkuChange}
|
|
||||||
/>
|
|
||||||
{sku.name} - {sku.price}
|
|
||||||
</Label>
|
|
||||||
))}
|
|
||||||
<Flex mt={3}>
|
|
||||||
<Button sx={{ flexGrow: 1 }} disabled={!stripe || loading}>
|
|
||||||
Donate
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal isOpen={isOpen || hasDonated} onRequestClose={handleClose}>
|
|
||||||
<Flex
|
|
||||||
sx={{
|
|
||||||
flexDirection: "column",
|
|
||||||
justifyContent: "center",
|
|
||||||
maxWidth: "300px",
|
|
||||||
flexGrow: 1,
|
|
||||||
}}
|
|
||||||
m={2}
|
|
||||||
>
|
|
||||||
{query.get("donated") === "true" ? donationSuccessful : donationForm}
|
|
||||||
{loading && <LoadingOverlay />}
|
|
||||||
<Banner isOpen={!!error} onRequestClose={() => setError(null)}>
|
|
||||||
<Box p={1}>
|
|
||||||
<Text as="p" variant="body2">
|
|
||||||
{error}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</Banner>
|
|
||||||
</Flex>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DonationModal;
|
|
@ -22,7 +22,7 @@ function About() {
|
|||||||
m={4}
|
m={4}
|
||||||
>
|
>
|
||||||
<Text my={2} variant="heading" as="h1" sx={{ fontSize: 5 }}>
|
<Text my={2} variant="heading" as="h1" sx={{ fontSize: 5 }}>
|
||||||
About ʕ•ᴥ•ʔ
|
About <span aria-hidden="true">ʕ•ᴥ•ʔ</span>
|
||||||
</Text>
|
</Text>
|
||||||
<Text my={1} mt={2} variant="heading" as="h3" sx={{ fontSize: 3 }}>
|
<Text my={1} mt={2} variant="heading" as="h3" sx={{ fontSize: 3 }}>
|
||||||
The Goal
|
The Goal
|
||||||
|
160
src/routes/Donate.js
Normal file
160
src/routes/Donate.js
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Flex,
|
||||||
|
Text,
|
||||||
|
Message,
|
||||||
|
Button,
|
||||||
|
Input,
|
||||||
|
Label,
|
||||||
|
Radio,
|
||||||
|
} from "theme-ui";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
|
import Footer from "../components/Footer";
|
||||||
|
import Banner from "../components/Banner";
|
||||||
|
import LoadingOverlay from "../components/LoadingOverlay";
|
||||||
|
|
||||||
|
const prices = [
|
||||||
|
{ price: "$5.00", name: "Small", value: 5 },
|
||||||
|
{ price: "$15.00", name: "Medium", value: 15 },
|
||||||
|
{ price: "$30.00", name: "Large", value: 30 },
|
||||||
|
];
|
||||||
|
function Donate() {
|
||||||
|
const location = useLocation();
|
||||||
|
const query = new URLSearchParams(location.search);
|
||||||
|
const hasDonated = query.has("success");
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
const [stripe, setStripe] = useState();
|
||||||
|
useEffect(() => {
|
||||||
|
import("@stripe/stripe-js").then(({ loadStripe }) => {
|
||||||
|
loadStripe(process.env.REACT_APP_STRIPE_API_KEY)
|
||||||
|
.then((stripe) => {
|
||||||
|
setStripe(stripe);
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
setError(err.message);
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
async function handleSubmit(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch("/create-session", { method: "POST" });
|
||||||
|
const session = await response.json();
|
||||||
|
const result = await stripe.redirectToCheckout({ sessionId: session.id });
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
setError(result.error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [selectedPrice, setSelectedPrice] = useState("Medium");
|
||||||
|
const [value, setValue] = useState(15);
|
||||||
|
|
||||||
|
function handlePriceChange(price) {
|
||||||
|
setValue(price.value);
|
||||||
|
setSelectedPrice(price.name);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
minHeight: "100%",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
flexDirection: "column",
|
||||||
|
maxWidth: "350px",
|
||||||
|
width: "100%",
|
||||||
|
flexGrow: 1,
|
||||||
|
}}
|
||||||
|
m={4}
|
||||||
|
as="form"
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
>
|
||||||
|
<Text my={2} variant="heading" as="h1" sx={{ fontSize: 5 }}>
|
||||||
|
Donate
|
||||||
|
</Text>
|
||||||
|
{hasDonated ? (
|
||||||
|
<Message my={2}>Thanks for donating!</Message>
|
||||||
|
) : (
|
||||||
|
<Text variant="body2" as="p">
|
||||||
|
In order to keep Owlbear Rodeo running any donation is greatly
|
||||||
|
appreciated.
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
<Text
|
||||||
|
my={4}
|
||||||
|
variant="heading"
|
||||||
|
as="h1"
|
||||||
|
sx={{ fontSize: 5, alignSelf: "center" }}
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
(ノ◕ヮ◕)ノ*:・゚✧
|
||||||
|
</Text>
|
||||||
|
<Text as="p" mb={2} variant="caption">
|
||||||
|
One time donation (USD)
|
||||||
|
</Text>
|
||||||
|
<Box sx={{ display: "flex", flexWrap: "wrap" }}>
|
||||||
|
{prices.map((price) => (
|
||||||
|
<Label mx={1} key={price.name} sx={{ width: "initial" }}>
|
||||||
|
<Radio
|
||||||
|
name="donation"
|
||||||
|
checked={selectedPrice === price.name}
|
||||||
|
onChange={() => handlePriceChange(price)}
|
||||||
|
/>
|
||||||
|
{price.price}
|
||||||
|
</Label>
|
||||||
|
))}
|
||||||
|
<Label mx={1} sx={{ width: "initial" }}>
|
||||||
|
<Radio
|
||||||
|
name="donation"
|
||||||
|
checked={selectedPrice === "Custom"}
|
||||||
|
onChange={() => handlePriceChange({ value, name: "Custom" })}
|
||||||
|
/>
|
||||||
|
Custom
|
||||||
|
</Label>
|
||||||
|
</Box>
|
||||||
|
{selectedPrice === "Custom" && (
|
||||||
|
<Box>
|
||||||
|
<Label htmlFor="donation">Amount ($)</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
name="donation"
|
||||||
|
min={1}
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => setValue(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<Button my={3} disabled={loading || !value}>
|
||||||
|
Go to Payment
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
<Footer />
|
||||||
|
{loading && <LoadingOverlay />}
|
||||||
|
<Banner isOpen={!!error} onRequestClose={() => setError(null)}>
|
||||||
|
<Box p={1}>
|
||||||
|
<Text as="p" variant="body2">
|
||||||
|
{error}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</Banner>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Donate;
|
@ -1,24 +1,24 @@
|
|||||||
import React, { useState, useEffect, useContext } from "react";
|
import React, { useState, useEffect, useContext } from "react";
|
||||||
import { Flex, Button, Image, Text, IconButton, Link } from "theme-ui";
|
import { Flex, Button, Image, Text, IconButton, Link } from "theme-ui";
|
||||||
|
import { useHistory } from "react-router-dom";
|
||||||
|
|
||||||
import Footer from "../components/Footer";
|
import Footer from "../components/Footer";
|
||||||
|
|
||||||
import StartModal from "../modals/StartModal";
|
import StartModal from "../modals/StartModal";
|
||||||
import JoinModal from "../modals/JoinModal";
|
import JoinModal from "../modals/JoinModal";
|
||||||
import DonateModal from "../modals/DonationModal";
|
|
||||||
|
|
||||||
import AuthContext from "../contexts/AuthContext";
|
import AuthContext from "../contexts/AuthContext";
|
||||||
|
|
||||||
import RedditIcon from "../icons/SocialRedditIcon";
|
import RedditIcon from "../icons/SocialRedditIcon";
|
||||||
import TwitterIcon from "../icons/SocialTwitterIcon";
|
import TwitterIcon from "../icons/SocialTwitterIcon";
|
||||||
import YouTubeIcon from "../icons/SocialYouTubeIcon";
|
import YouTubeIcon from "../icons/SocialYouTubeIcon";
|
||||||
|
import DonateIcon from "../icons/DonateIcon";
|
||||||
|
|
||||||
import owlington from "../images/Owlington.png";
|
import owlington from "../images/Owlington.png";
|
||||||
|
|
||||||
function Home() {
|
function Home() {
|
||||||
const [isStartModalOpen, setIsStartModalOpen] = useState(false);
|
const [isStartModalOpen, setIsStartModalOpen] = useState(false);
|
||||||
const [isJoinModalOpen, setIsJoinModalOpen] = useState(false);
|
const [isJoinModalOpen, setIsJoinModalOpen] = useState(false);
|
||||||
const [isDonateModalOpen, setIsDonateModalOpen] = useState(false);
|
|
||||||
|
|
||||||
// Reset password on visiting home
|
// Reset password on visiting home
|
||||||
const { setPassword } = useContext(AuthContext);
|
const { setPassword } = useContext(AuthContext);
|
||||||
@ -26,6 +26,8 @@ function Home() {
|
|||||||
setPassword("");
|
setPassword("");
|
||||||
}, [setPassword]);
|
}, [setPassword]);
|
||||||
|
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
sx={{
|
sx={{
|
||||||
@ -58,13 +60,23 @@ function Home() {
|
|||||||
Beta v{process.env.REACT_APP_VERSION}
|
Beta v{process.env.REACT_APP_VERSION}
|
||||||
</Text>
|
</Text>
|
||||||
<Button
|
<Button
|
||||||
m={2}
|
as="a"
|
||||||
onClick={() => setIsDonateModalOpen(true)}
|
href="/donate"
|
||||||
variant="secondary"
|
my={4}
|
||||||
|
mx={2}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
history.push("/donate");
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "flex-end",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Support Us
|
Donate <DonateIcon />
|
||||||
</Button>
|
</Button>
|
||||||
<Flex sx={{ justifyContent: "center" }}>
|
<Flex mb={4} mt={0} sx={{ justifyContent: "center" }}>
|
||||||
<Link href="https://www.reddit.com/r/OwlbearRodeo/">
|
<Link href="https://www.reddit.com/r/OwlbearRodeo/">
|
||||||
<IconButton title="Reddit" aria-label="Reddit">
|
<IconButton title="Reddit" aria-label="Reddit">
|
||||||
<RedditIcon />
|
<RedditIcon />
|
||||||
@ -89,10 +101,6 @@ function Home() {
|
|||||||
isOpen={isStartModalOpen}
|
isOpen={isStartModalOpen}
|
||||||
onRequestClose={() => setIsStartModalOpen(false)}
|
onRequestClose={() => setIsStartModalOpen(false)}
|
||||||
/>
|
/>
|
||||||
<DonateModal
|
|
||||||
isOpen={isDonateModalOpen}
|
|
||||||
onRequestClose={() => setIsDonateModalOpen(false)}
|
|
||||||
/>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
<Footer />
|
<Footer />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
Loading…
Reference in New Issue
Block a user