diff --git a/src/App.js b/src/App.js index f5afa39..9e93684 100644 --- a/src/App.js +++ b/src/App.js @@ -8,25 +8,29 @@ import Game from "./routes/Game"; import About from "./routes/About"; import FAQ from "./routes/FAQ"; +import { AuthProvider } from "./contexts/AuthContext"; + function App() { return ( - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + ); } diff --git a/src/components/Modal.js b/src/components/Modal.js index 1b9bd00..94b9eda 100644 --- a/src/components/Modal.js +++ b/src/components/Modal.js @@ -2,7 +2,13 @@ import React from "react"; import Modal from "react-modal"; import { useThemeUI, Close } from "theme-ui"; -function StyledModal({ isOpen, onRequestClose, children, ...props }) { +function StyledModal({ + isOpen, + onRequestClose, + children, + allowClose, + ...props +}) { const { theme } = useThemeUI(); return ( @@ -25,13 +31,19 @@ function StyledModal({ isOpen, onRequestClose, children, ...props }) { {...props} > {children} - + {allowClose && ( + + )} ); } +StyledModal.defaultProps = { + allowClose: true, +}; + export default StyledModal; diff --git a/src/contexts/AuthContext.js b/src/contexts/AuthContext.js new file mode 100644 index 0000000..ace0b95 --- /dev/null +++ b/src/contexts/AuthContext.js @@ -0,0 +1,25 @@ +import React, { useState, useEffect } from "react"; + +const AuthContext = React.createContext(); + +export function AuthProvider({ children }) { + const [password, setPassword] = useState( + sessionStorage.getItem("auth") || "" + ); + + useEffect(() => { + sessionStorage.setItem("auth", password); + }, [password]); + + const [authenticationStatus, setAuthenticationStatus] = useState("unknown"); + + const value = { + password, + setPassword, + authenticationStatus, + setAuthenticationStatus, + }; + return {children}; +} + +export default AuthContext; diff --git a/src/helpers/useSession.js b/src/helpers/useSession.js index caf67cf..0def7a4 100644 --- a/src/helpers/useSession.js +++ b/src/helpers/useSession.js @@ -1,9 +1,11 @@ -import { useEffect, useState } from "react"; +import { useEffect, useState, useContext } from "react"; import io from "socket.io-client"; import { omit } from "../helpers/shared"; import Peer from "../helpers/Peer"; +import AuthContext from "../contexts/AuthContext"; + const socket = io("https://broker.owlbear.rodeo"); function useSession( @@ -15,9 +17,11 @@ function useSession( onPeerTrackRemoved, onPeerError ) { + const { password, setAuthenticationStatus } = useContext(AuthContext); + useEffect(() => { - socket.emit("join party", partyId); - }, [partyId]); + socket.emit("join party", partyId, password); + }, [partyId, password]); const [peers, setPeers] = useState({}); @@ -148,6 +152,7 @@ function useSession( const sync = index === 0; addPeer(id, true, sync); } + setAuthenticationStatus("authenticated"); } function handleSignal(data) { @@ -157,17 +162,23 @@ function useSession( } } + function handleAuthError() { + setAuthenticationStatus("unauthenticated"); + } + socket.on("party member joined", handlePartyMemberJoined); socket.on("party member left", handlePartyMemberLeft); socket.on("joined party", handleJoinedParty); socket.on("signal", handleSignal); + socket.on("auth error", handleAuthError); return () => { socket.removeListener("party member joined", handlePartyMemberJoined); socket.removeListener("party member left", handlePartyMemberLeft); socket.removeListener("joined party", handleJoinedParty); socket.removeListener("signal", handleSignal); + socket.removeListener("auth error", handleAuthError); }; - }, [peers]); + }, [peers, setAuthenticationStatus]); return { peers, socket }; } diff --git a/src/modals/AuthModal.js b/src/modals/AuthModal.js new file mode 100644 index 0000000..e97b0a8 --- /dev/null +++ b/src/modals/AuthModal.js @@ -0,0 +1,52 @@ +import React, { useState, useContext, useRef } from "react"; +import { Box, Input, Button, Label, Flex } from "theme-ui"; + +import AuthContext from "../contexts/AuthContext"; + +import Modal from "../components/Modal"; + +function AuthModal({ isOpen }) { + const { password, setPassword, setAuthenticationStatus } = useContext( + AuthContext + ); + const [tmpPassword, setTempPassword] = useState(password); + + function handleChange(event) { + setTempPassword(event.target.value); + } + + function handleSubmit(event) { + event.preventDefault(); + setAuthenticationStatus("unknown"); + setPassword(tmpPassword); + } + + const inputRef = useRef(); + function focusInput() { + inputRef.current && inputRef.current.focus(); + } + + return ( + + + + + + + + + + ); +} + +export default AuthModal; diff --git a/src/modals/JoinModal.js b/src/modals/JoinModal.js index c28d655..32d677b 100644 --- a/src/modals/JoinModal.js +++ b/src/modals/JoinModal.js @@ -48,7 +48,11 @@ function JoinModal({ isOpen, onRequestClose }) { onChange={handleChange} ref={inputRef} /> - + + + diff --git a/src/modals/StartModal.js b/src/modals/StartModal.js new file mode 100644 index 0000000..91d274c --- /dev/null +++ b/src/modals/StartModal.js @@ -0,0 +1,73 @@ +import React, { useState, useContext } from "react"; +import { Box, Label, Input, Button, Flex, Checkbox } from "theme-ui"; +import { useHistory } from "react-router-dom"; +import shortid from "shortid"; + +import AuthContext from "../contexts/AuthContext"; + +import Modal from "../components/Modal"; + +function StartModal({ isOpen, onRequestClose }) { + let history = useHistory(); + const { password, setPassword } = useContext(AuthContext); + + function handlePasswordChange(event) { + setPassword(event.target.value); + } + + const [usePassword, setUsePassword] = useState(true); + function handleUsePasswordChange(event) { + setUsePassword(event.target.checked); + } + + function handleSubmit(event) { + event.preventDefault(); + if (!usePassword) { + setPassword(""); + } + history.push(`/game/${shortid.generate()}`); + } + + return ( + + + + + + + + + + + + + + + ); +} + +export default StartModal; diff --git a/src/routes/Game.js b/src/routes/Game.js index 5f125f7..7ac4d95 100644 --- a/src/routes/Game.js +++ b/src/routes/Game.js @@ -1,9 +1,14 @@ -import React, { useState, useRef, useEffect, useCallback } from "react"; +import React, { + useState, + useRef, + useEffect, + useCallback, + useContext, +} from "react"; import { Flex, Box, Text, Link } from "theme-ui"; import { useParams } from "react-router-dom"; import { omit, isStreamStopped } from "../helpers/shared"; - import useSession from "../helpers/useSession"; import { getRandomMonster } from "../helpers/monsters"; @@ -12,8 +17,13 @@ import Tokens from "../components/Tokens"; import Map from "../components/Map"; import Banner from "../components/Banner"; +import AuthModal from "../modals/AuthModal"; + +import AuthContext from "../contexts/AuthContext"; + function Game() { const { id: gameId } = useParams(); + const { authenticationStatus } = useContext(AuthContext); const { peers, socket } = useSession( gameId, @@ -238,6 +248,7 @@ function Game() { + ); } diff --git a/src/routes/Home.js b/src/routes/Home.js index 7cd6158..2e9d967 100644 --- a/src/routes/Home.js +++ b/src/routes/Home.js @@ -1,22 +1,25 @@ -import React, { useState } from "react"; +import React, { useState, useEffect, useContext } from "react"; import { Flex, Button, Image, Text } from "theme-ui"; -import shortid from "shortid"; -import { useHistory } from "react-router-dom"; import Footer from "../components/Footer"; +import StartModal from "../modals/StartModal"; import JoinModal from "../modals/JoinModal"; +import AuthContext from "../contexts/AuthContext"; + import owlington from "../images/Owlington.png"; function Home() { - let history = useHistory(); - function handleStartGame() { - history.push(`/game/${shortid.generate()}`); - } - + const [isStartModalOpen, setIsStartModalOpen] = useState(false); const [isJoinModalOpen, setIsJoinModalOpen] = useState(false); + // Reset password on visiting home + const { setPassword } = useContext(AuthContext); + useEffect(() => { + setPassword(""); + }, [setPassword]); + return ( -