diff --git a/package-lock.json b/package-lock.json index 8ff10ef703c2a16cc7ca0e024a55b024df857f12..a5d09a513ce24f8fbb0dba8bc7923b341a5fc223 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5739,6 +5739,11 @@ "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" }, + "gud": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", + "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" + }, "gzip-size": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.0.0.tgz", @@ -5933,6 +5938,19 @@ "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" }, + "history": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/history/-/history-4.9.0.tgz", + "integrity": "sha512-H2DkjCjXf0Op9OAr6nJ56fcRkTSNrUiv41vNJ6IswJjif6wlpZK0BTfFbi7qK9dXLSYZxkq5lBsj3vUjlYBYZA==", + "requires": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^2.2.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^0.4.0" + } + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -8257,6 +8275,16 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" }, + "mini-create-react-context": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz", + "integrity": "sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw==", + "requires": { + "@babel/runtime": "^7.4.0", + "gud": "^1.0.0", + "tiny-warning": "^1.0.2" + } + }, "mini-css-extract-plugin": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.5.0.tgz", @@ -10393,6 +10421,52 @@ "lodash-es": "^4.17.10" } }, + "react-router": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.0.1.tgz", + "integrity": "sha512-EM7suCPNKb1NxcTZ2LEOWFtQBQRQXecLxVpdsP4DW4PbbqYWeRiLyV/Tt1SdCrvT2jcyXAXmVTmzvSzrPR63Bg==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "mini-create-react-context": "^0.3.0", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "requires": { + "isarray": "0.0.1" + } + } + } + }, + "react-router-dom": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.0.1.tgz", + "integrity": "sha512-zaVHSy7NN0G91/Bz9GD4owex5+eop+KvgbxXsP/O+iW1/Ln+BrJ8QiIR5a6xNPtrdTvLkxqlDClx13QO1uB8CA==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.0.1", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + } + }, "react-scripts": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.0.1.tgz", @@ -10787,6 +10861,11 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" }, + "resolve-pathname": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz", + "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg==" + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -11986,6 +12065,16 @@ "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" }, + "tiny-invariant": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.5.tgz", + "integrity": "sha512-BziszNEQNwtyMS9OVJia2LK9N9b6VJ35kBrvhDDDpr4hreLYqhCie15dB35uZMdqv9ZTQ55GHQtkz2FnleTHIA==" + }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "tinycolor2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", @@ -12423,6 +12512,11 @@ "spdx-expression-parse": "^3.0.0" } }, + "value-equal": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz", + "integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index f1ae9df1c88c4b64dcbc03f7f700cb9a6e613233..91b670f85cb70e95340396820573962e7fb8e5fe 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "react-dom": "^16.8.6", "react-leaflet": "^2.3.0", "react-leaflet-draw": "0.19.0", + "react-router-dom": "^5.0.1", "react-scripts": "3.0.1", "universal-cookie": "^4.0.0", "socket.io": "^2.2.0", diff --git a/src/App.css b/src/App.css index bccff582ded131e5f820cb21d76930d2f271641d..b3004ac40734d1b05cd893b254948d77559da7dc 100644 --- a/src/App.css +++ b/src/App.css @@ -2,6 +2,8 @@ body { margin: 0; padding: 0; overflow: hidden; + background-color: #1d1d1b; + color: #ffffff; } .hidden { @@ -199,11 +201,28 @@ div.login button:hover { text-shadow: none; font-weight: normal; } + +.gamecard { + border: 5px solid #647828; + border-radius: 20px; + padding: 10px; + margin: 10px; + background-color: #859b28; + color: #ffffff; +} + +.gamelist { + background-color: #1d1d1b; + max-width: 500px; + padding: 10px; +} + .tasklist { position: absolute; - margin-top: 50px; + margin-top: 100px; z-index: 2020; background-color: #ffffff80; + color: black; } .tasklist-item { diff --git a/src/App.js b/src/App.js index 013fe4216ec2016aaee09be3ca6be530c5c1b3a9..b701a6e8845cb04a430eb2d0baaa03976bc061ec 100644 --- a/src/App.js +++ b/src/App.js @@ -1,22 +1,27 @@ import React, { Component } from "react"; import "../node_modules/leaflet-draw/dist/leaflet.draw.css"; import "./App.css"; -import UserMap from "./components/UserMap"; -import Header from "./components/Header"; import ClientSocket from "./components/Socket"; +import { + BrowserRouter as Router, + Route, + Switch, + Redirect +} from "react-router-dom"; +import LoginForm from "./components/LoginForm"; +import RegisterForm from "./components/RegisterForm"; +import GameSelection from "./components/GameSelection"; +import GameView from "./components/GameView"; -class App extends Component { +export default class App extends Component { constructor() { super(); // set initial state this.state = { - lat: 62.2416479, - lng: 25.7597186, - zoom: 13, - mapUrl: "https://tiles.kartat.kapsi.fi/taustakartta/{z}/{x}/{y}.jpg", - currentGameId: null, - socketSignal: null + socketSignal: null, + logged: false, + authenticateComplete: false }; } // Toggles through the list and changes the mapUrl state @@ -56,31 +61,150 @@ class App extends Component { } ); }; + handleState = data => { + sessionStorage.setItem("name", data.name); + sessionStorage.setItem("token", data.token); + this.setState({ logged: true }); + }; + + // verifies the token (if it exists) on element mount with backend server + componentDidMount() { + let token = sessionStorage.getItem("token"); + if (token) { + fetch(`${process.env.REACT_APP_API_URL}/user/verify`, { + headers: { + Authorization: "Bearer " + token + } + }) + .then(res => res.json()) + .then( + result => { + // if token is still valid, login user + if (result === true) { + this.setState({ + logged: true + }); + // logout user if token has expired / is invalid + } else { + this.handleLogout(); + } + }, + error => { + console.log(error); + } + ) + .then(() => { + this.setState({ + authenticateComplete: true + }); + }); + } else { + this.setState({ + authenticateComplete: true + }); + } + } + + loginForm = () => { + return ( + <Route + render={props => + !this.state.logged ? ( + <LoginForm view="" handleState={this.handleState} /> + ) : ( + <Redirect + to={{ + pathname: "/" + }} + /> + ) + } + /> + ); + }; + registerForm = () => { + return ( + <Route + render={props => + !this.state.logged ? ( + <RegisterForm view="" handleState={this.handleState} /> + ) : ( + <Redirect + to={{ + pathname: "/" + }} + /> + ) + } + /> + ); + }; render() { - const initialPosition = [this.state.lat, this.state.lng]; + // TODO: think better solution to wait for authenticator + if (!this.state.authenticateComplete) { + return <div>Authenticating...</div>; + } + return ( <div> - <UserMap - position={initialPosition} - zoom={this.state.zoom} - mapUrl={this.state.mapUrl} - currentGameId={this.state.currentGameId} - socketSignal={this.state.socketSignal} - /> - <Header - handleLayerChange={this.handleLayerChange} - handleGameChange={this.handleGameChange} - /> {this.state.currentGameId && ( <ClientSocket gameId={this.state.currentGameId} getSocketSignal={this.getSocketSignal} /> )} + + <Router> + <div> + {/* Debug Sign out button ------------------------ */} + {this.state.logged && ( + <div> + <label>Logged in: {sessionStorage.getItem("name")}</label> + <button + onClick={() => { + sessionStorage.setItem("token", ""); + this.setState({ logged: false }); + }} + > + Sign out + </button> + </div> + )} + {/* Debug End ----------------------- */} + + {!this.state.logged && ( + <Switch> + <Route exact path="/replay" component={this.replay} /> + <Route exact path="/register" component={this.registerForm} /> + <Route exact path="/" component={this.loginForm} /> + {/* Redirect from any other path to root */} + <Redirect from="*" to="/" /> + </Switch> + )} + + {this.state.logged && ( + <Switch> + <Route + path="/game" + component={() => { + return <GameView />; + }} + /> + <Route + exact + path="/" + component={() => { + return <GameSelection />; + }} + /> + {/* Redirect from any other path to root */} + <Redirect from="*" to="/" /> + </Switch> + )} + </div> + </Router> </div> ); } } - -export default App; diff --git a/src/components/EditGameForm.js b/src/components/EditGameForm.js index 55247e638269ca8cb9d888d972cc9ec75aa08857..da85c6dd00d93a634bebb935d5eee31748260352 100644 --- a/src/components/EditGameForm.js +++ b/src/components/EditGameForm.js @@ -1,10 +1,10 @@ -import React, { Fragment } from "react"; +import React from "react"; import ReactDOM from "react-dom"; import { Map, TileLayer } from "react-leaflet"; import { SketchPicker } from "react-color"; import reactCSS from "reactcss"; -export class EditGameForm extends React.Component { +export default class EditGameForm extends React.Component { constructor(props) { super(props); @@ -177,6 +177,7 @@ export class EditGameForm extends React.Component { .then(result => { console.log(result); this.handleView(); + this.props.onEditSave(); }) .catch(error => console.log(error)); } @@ -267,6 +268,7 @@ export class EditGameForm extends React.Component { }) .then(result => { alert(result.message); + this.props.onEditSave(); this.handleView(); }) .catch(error => console.log("Error: ", error)); @@ -305,6 +307,7 @@ export class EditGameForm extends React.Component { .then(result => { let factions = result.map(faction => { return { + factionId: faction.factionId, factionName: faction.factionName, factionPassword: faction.factionPassword, multiplier: 1, @@ -315,6 +318,7 @@ export class EditGameForm extends React.Component { // Remove objective point's id from the object let objectivePoints = json.objective_points.map(point => { return { + objectivePointId: point.objectivePointId, objectivePointDescription: point.objectivePointDescription, objectivePointMultiplier: point.objectivePointMultiplier }; @@ -642,5 +646,3 @@ export class EditGameForm extends React.Component { ); } } - -export default EditGameForm; diff --git a/src/components/GameCard.js b/src/components/GameCard.js new file mode 100644 index 0000000000000000000000000000000000000000..b3c5a8cf573caefe7d209df8f30e3cd4697a29e3 --- /dev/null +++ b/src/components/GameCard.js @@ -0,0 +1,62 @@ +import React from "react"; +import { Link } from "react-router-dom"; + +export default class GameCard extends React.Component { + state = { + editForm: false, + gameInfo: {} + }; + + // Get game info + componentDidMount() { + this.getGameInfo(); + } + + getGameInfo() { + fetch(`${process.env.REACT_APP_API_URL}/game/${this.props.gameId}`) + .then(res => { + if (res.ok) { + return res.json(); + } + }) + .then(res => { + this.setState({ + gameInfo: { + id: res.id, + name: res.name, + desc: res.desc, + state: res.state, + startdate: res.startdate, + enddate: res.enddate + } + }); + }) + .catch(error => console.log(error)); + } + + render() { + if (this.state.gameInfo.id === undefined) { + return false; + } + + return ( + <div className="gamecard"> + <label>Name: {this.state.gameInfo.name}</label> + <br /> + <label>Description: {this.state.gameInfo.desc}</label> + <br /> + <label> + Date: {this.state.gameInfo.startdate} - {this.state.gameInfo.enddate} + </label> + <br /> + <label>State: {this.state.gameInfo.state}</label> + <br /> + <Link + to={{ pathname: "/game", search: "?id=" + this.state.gameInfo.id }} + > + <button type="button">Select</button> + </Link> + </div> + ); + } +} diff --git a/src/components/GameList.js b/src/components/GameList.js index 9a56d3626985c58608d92bbcde2b28720829a536..fcb3091c15867045b985157192b864789dabc204 100644 --- a/src/components/GameList.js +++ b/src/components/GameList.js @@ -1,6 +1,5 @@ import React, { Fragment } from "react"; -import EditGameForm from "./EditGameForm"; -import JoinGameForm from "./JoinGameForm"; +import GameCard from "./GameCard"; class GameList extends React.Component { constructor(props) { @@ -81,45 +80,19 @@ class GameList extends React.Component { }; render() { - let items = []; - - for (let i = 0; i < this.state.games.length; i++) { - const element = this.state.games[i]; - items.push( - <option key={element.id} value={element.id}> - {element.name} - </option> + let gamelistItems = this.props.games.map(game => { + return ( + <GameCard + key={game.id} + gameId={game.id} + onEditSave={this.props.onEditSave} + /> ); - } + }); return ( <Fragment> - <label>Game: </label> - <select id="changeActiveGameList" onChange={this.handleChange}> - {items} - </select> - {sessionStorage.getItem("token") && ( - <Fragment> - <button id="editGameButton" onClick={this.handleEditClick}> - Edit game - </button> - <button id="editGameButton" onClick={this.handleJoinClick}> - Join Game - </button> - </Fragment> - )} - {this.state.editForm && this.state.selectedGame !== undefined && ( - <EditGameForm - gameId={this.state.selectedGame} - toggleView={this.toggleView} - /> - )} - {this.state.joinForm && this.state.selectedGame !== undefined && ( - <JoinGameForm - gameId={this.state.selectedGame} - toggleView={this.toggleView} - /> - )} + <div className="gamelist">{gamelistItems}</div> </Fragment> ); } diff --git a/src/components/GameSelection.js b/src/components/GameSelection.js new file mode 100644 index 0000000000000000000000000000000000000000..9759aec330ff953a7c6d13c50ecc89860ccade69 --- /dev/null +++ b/src/components/GameSelection.js @@ -0,0 +1,61 @@ +import React from "react"; +import GameList from "./GameList"; +import NewGameForm from "./NewGameForm"; + +export default class GameSelection extends React.Component { + state = { + newGameForm: false, + games: [] + }; + + componentDidMount() { + this.getGames(); + } + + getGames() { + fetch(`${process.env.REACT_APP_API_URL}/game/listgames`) + .then(response => response.json()) + .then(games => { + this.setState({ + games: games + }); + // taking the initialized gameID to UserMap.js (GameList.js -> Header.js -> App.js -> UserMap.js) + //this.props.handleGameChange(games[0].id); + }) + .catch(error => { + console.log(error); + }); + } + + render() { + return ( + <div> + <label>Games</label> + <br /> + <button + id="newGameButton" + onClick={() => this.setState({ newGameForm: true })} + > + New Game + </button> + {this.state.newGameForm && ( + <NewGameForm + view="" + handleState={this.handleState} + toggleView={() => + this.setState({ newGameForm: false }, () => { + this.getGames(); + }) + } + /> + )} + <GameList + games={this.state.games} + onEditSave={() => { + this.getGames(); + }} + /> + </div> + ); + } +} diff --git a/src/components/GameStateButtons.js b/src/components/GameStateButtons.js new file mode 100644 index 0000000000000000000000000000000000000000..c770ed7896facdddaf56adecde4c689d10dbeb3f --- /dev/null +++ b/src/components/GameStateButtons.js @@ -0,0 +1,75 @@ +import React, { Fragment } from "react"; + +export default class GameStateButtons extends React.Component { + state = { + gameState: this.props.gameState // valid values: CREATED,STARTED,PAUSED,ENDED + }; + + setGameState(state) { + console.log(state); + let token = sessionStorage.getItem("token"); + let error = false; + fetch( + `${process.env.REACT_APP_API_URL}/game/edit-state/${this.props.gameId}`, + { + method: "PUT", + headers: { + Authorization: "Bearer " + token, + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ + id: this.props.gameId, + state: state + }) + } + ) + .then(res => { + if (!res.ok) { + error = true; + } + return res.json(); + }) + .then(res => { + if (error) { + console.log(res); + } else { + alert(`Game state changed to ${state}`); + this.setState({ gameState: state }); + } + }) + .catch(error => console.log(error)); + } + + render() { + if (this.state.gameState === "CREATED") { + return ( + <button onClick={() => this.setGameState("STARTED")}>Start</button> + ); + } + + if (this.state.gameState === "STARTED") { + return ( + <Fragment> + <button onClick={() => this.setGameState("PAUSED")}>Pause</button> + <button onClick={() => this.setGameState("ENDED")}>Stop</button> + </Fragment> + ); + } + + if (this.state.gameState === "PAUSED") { + return ( + <Fragment> + <button onClick={() => this.setGameState("STARTED")}>Continue</button> + <button onClick={() => this.setGameState("ENDED")}>Stop</button> + </Fragment> + ); + } + + if (this.state.gameState === "ENDED") { + return "The game has ended"; + } + + return false; + } +} diff --git a/src/components/GameView.js b/src/components/GameView.js new file mode 100644 index 0000000000000000000000000000000000000000..217f292fc46e7299e005b7eb0eb52a78af2f8f73 --- /dev/null +++ b/src/components/GameView.js @@ -0,0 +1,191 @@ +import React from "react"; +import UserMap from "./UserMap"; +import TaskListButton from "./TaskListButton"; +import { Link } from "react-router-dom"; +import EditGameForm from "./EditGameForm"; +import JoinGameForm from "./JoinGameForm"; +import PlayerlistView from "./PlayerlistView"; +import NotificationView from "./NotificationView"; +import GameStateButtons from "./GameStateButtons"; + +export default class GameView extends React.Component { + state = { + gameInfo: null, + role: "", //empty, soldier, factionleader, admin + form: "", + lat: 62.2416479, + lng: 25.7597186, + zoom: 13, + mapUrl: "https://tiles.kartat.kapsi.fi/taustakartta/{z}/{x}/{y}.jpg" + }; + + componentDidMount() { + let gameId = new URL(window.location.href).searchParams.get("id"); + let token = sessionStorage.getItem("token"); + + fetch(`${process.env.REACT_APP_API_URL}/game/${gameId}`) + .then(res => { + if (!res.ok) { + throw Error(); + } + }) + .catch(error => { + alert("Game not found"); + window.document.location.href = "/"; + }); + + // Get game info + fetch(`${process.env.REACT_APP_API_URL}/game/${gameId}`) + .then(res => res.json()) + .then(res => { + this.setState({ + gameInfo: res + }); + }) + .catch(error => console.log(error)); + + // Get Role + fetch(`${process.env.REACT_APP_API_URL}/faction/check-faction/${gameId}`, { + method: "GET", + headers: { + Authorization: "Bearer " + token + } + }) + .then(res => res.json()) + .then(res => { + this.setState({ role: res.role }); + }) + .catch(error => console.log(error)); + } + + handleLeaveFaction = e => { + let token = sessionStorage.getItem("token"); + let error = false; + fetch( + `${process.env.REACT_APP_API_URL}/faction/leave/${ + this.state.gameInfo.id + }`, + { + method: "DELETE", + headers: { + Authorization: "Bearer " + token + } + } + ) + .then(res => { + if (!res.ok) { + error = true; + } + return res.json(); + }) + .then(res => { + alert(res.message); + }) + .catch(error => console.log(error)); + }; + + render() { + const initialPosition = [this.state.lat, this.state.lng]; + + return ( + <div> + <Link to="/"> + <button>Game selection</button> + </Link> + {this.state.gameInfo !== null && ( + <div> + <div>Game Name: {this.state.gameInfo.name}</div> + {this.state.role === "" && ( + <div>You don't have a role in this game</div> + )} + {this.state.role !== "" && ( + <div>Your role in this game: {this.state.role}</div> + )} + {this.state.role === "admin" && ( + <button + id="editGameButton" + onClick={() => this.setState({ form: "edit" })} + > + Edit + </button> + )} + {this.state.role === "" && ( + <button + id="joinGameButton" + onClick={() => this.setState({ form: "join" })} + > + Join + </button> + )} + <button + id="showPlayersButton" + onClick={() => this.setState({ form: "players" })} + > + Players + </button> + {this.state.role !== "" && ( + <button + id="notificationsButton" + onClick={() => this.setState({ form: "notifications" })} + > + Notifications + </button> + )} + {this.state.role !== "" && ( + <TaskListButton + gameId={this.state.gameInfo.id} + role={this.state.role} + /> + )} + {this.state.role !== "admin" && this.state.role !== "" && ( + <button id="leaveFactionButton" onClick={this.handleLeaveFaction}> + Leave Faction + </button> + )} + {this.state.role === "admin" && ( + <GameStateButtons + gameState={this.state.gameInfo.state} + gameId={this.state.gameInfo.id} + /> + )} + <UserMap + position={initialPosition} + zoom={this.state.zoom} + mapUrl={this.state.mapUrl} + currentGameId={this.state.gameInfo.id} + /> + {this.state.form === "edit" && ( + <EditGameForm + gameId={this.state.gameInfo.id} + toggleView={() => this.setState({ form: "" })} + onEditSave={() => { + this.getGameInfo(); + }} + /> + )} + {this.state.form === "join" && ( + <JoinGameForm + gameId={this.state.gameInfo.id} + toggleView={() => this.setState({ form: "" })} + onJoin={() => console.log("joinde")} + /> + )} + {this.state.form === "players" && ( + <PlayerlistView + gameId={this.state.gameInfo.id} + role={this.state.role} + toggleView={() => this.setState({ form: "" })} + /> + )} + {this.state.form === "notifications" && ( + <NotificationView + gameId={this.state.gameInfo.id} + toggleView={() => this.setState({ form: "" })} + /> + )} + </div> + )} + </div> + ); + } +} diff --git a/src/components/Header.js b/src/components/Header.js index 1faedbbcd01873cc0179bfe91c29e38d3ab50b0f..9269ea1b8aaa28fd99d0aa8583f289785dadcdc6 100644 --- a/src/components/Header.js +++ b/src/components/Header.js @@ -1,15 +1,10 @@ import React from "react"; - import LoginForm from "./LoginForm"; import RegisterForm from "./RegisterForm"; import TaskListButton from "./TaskListButton"; import GameSidebar from "./GameSidebar"; class Header extends React.Component { - constructor(props) { - super(props); - } - state = { form: "", // Popup form (login, register etc.) username: null, diff --git a/src/components/JoinGameForm.js b/src/components/JoinGameForm.js index 7ea0aaf958683a807ee73ed23abe19882f591fe7..f4f89edcacf82bbe4281dc5b419f694de0689eb6 100644 --- a/src/components/JoinGameForm.js +++ b/src/components/JoinGameForm.js @@ -1,13 +1,20 @@ import React from "react"; +import ReactDOM from "react-dom"; +import PropTypes from "prop-types"; export default class JoinGameForm extends React.Component { constructor(props) { super(props); this.state = { - gameJSON: undefined + gameJSON: undefined, + selectedFactionId: "", + factionPasswordInput: "", //>= 3 chars + errorMessage: "" }; } + // Get game info + //TODO: from props componentDidMount() { if (this.props.gameId === undefined) { alert("game not selected"); @@ -16,26 +23,118 @@ export default class JoinGameForm extends React.Component { .then(result => result.json()) .then(json => { this.setState({ - gameJSON: json + gameJSON: json, + selectedFactionId: + json.factions.length > 0 ? json.factions[0].factionId : "" }); }) .catch(error => console.log(error)); } } + handleView = e => { + this.props.toggleView(this.props.view); + }; + + handleGameJoin = e => { + e.preventDefault(); + + let token = sessionStorage.getItem("token"); + let error = false; + + fetch( + `${process.env.REACT_APP_API_URL}/faction/join-faction/${ + this.state.gameJSON.id + }`, + { + method: "PUT", + headers: { + Authorization: "Bearer " + token, + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ + factionPassword: this.state.factionPasswordInput, + factionId: this.state.selectedFactionId, + game: this.state.gameJSON.id + }) + } + ) + .then(res => { + if (!res.ok) { + error = true; + } + return res.json(); + }) + .then(result => { + if (!error) { + alert("Joined faction " + result.faction.factionName); + this.props.onJoin(); + this.handleView(); + } else { + alert(result.message); + } + }) + .catch(error => console.log(error)); + }; + render() { if (this.state.gameJSON === undefined) { return false; } - return ( - <div> - <form> - <label>Join game: {this.state.gameJSON.name}</label> - <div>{this.state.gameJSON.desc}</div> - <button onClick={() => console.log("clicked")}>Submit</button> - </form> - </div> + let factionItems = this.state.gameJSON.factions.map(faction => ( + <option key={faction.factionId} value={faction.factionId}> + {faction.factionName} + </option> + )); + + return ReactDOM.createPortal( + <div className="fade-main"> + <div className="sticky"> + <span + id="closeNewGameFormX" + className="close" + onClick={this.handleView} + > + × + </span> + </div> + <div className=""> + <form onSubmit={this.handleGameJoin}> + <h1>Join game: {this.state.gameJSON.name}</h1> + <h2>Description: {this.state.gameJSON.desc}</h2> + <select + onChange={e => + this.setState({ selectedFactionId: e.target.value }) + } + > + {factionItems} + </select> + <input + id="factionPasswordInput" + value={this.state.factionPasswordInput} + onChange={e => + this.setState({ factionPasswordInput: e.target.value }) + } + placeholder="Password" + minLength="3" + required + /> + <button id="joinGameSubmitButton" type="submit"> + Submit + </button> + <h2>{this.state.errorMsg}</h2> + </form> + </div> + </div>, + document.getElementById("form") ); } } + +JoinGameForm.propTypes = { + gameId: PropTypes.string, + toggleView: PropTypes.func, + onJoin: PropTypes.func +}; diff --git a/src/components/LoginForm.js b/src/components/LoginForm.js index dc993717505f6ce237391bd05d892b2c63117110..b71777f007071ead97db3da75187caa80728a10d 100644 --- a/src/components/LoginForm.js +++ b/src/components/LoginForm.js @@ -1,4 +1,5 @@ import React from "react"; +import { Link } from "react-router-dom"; export class LoginForm extends React.Component { constructor(props) { @@ -20,18 +21,6 @@ export class LoginForm extends React.Component { this.setState({ [name]: value }); }; - // show/hide this form - handleView = e => { - this.props.toggleView(this.props.view); - }; - - // remove login view with ESC - handleEsc = e => { - if (e.keyCode === 27) { - this.handleView(); - } - }; - handleLogin = e => { const name = this.state.username; const password = this.state.password; @@ -54,7 +43,6 @@ export class LoginForm extends React.Component { result => { if (result.name) { this.props.handleState(result); - this.handleView(); } else { this.handleError(result.message); } @@ -68,29 +56,12 @@ export class LoginForm extends React.Component { ); }; - componentDidMount() { - document.addEventListener("keyup", this.handleEsc); - } - - componentWillUnmount() { - document.removeEventListener("keyup", this.handleEsc); - } - render() { return ( <div className="fade-main"> - <div className="sticky"> - <span - id="closeLoginFormX" - className="close" - onClick={this.handleView} - > - × - </span> - </div> <div className="login"> <form onSubmit={this.handleLogin}> - <h1>demo login</h1> + <h1>Login</h1> <br /> <input placeholder="Enter Username" @@ -111,10 +82,13 @@ export class LoginForm extends React.Component { /> <br /> <button id="submitLoginButton" type="submit"> - login + Submit </button> <h2>{this.state.errorMsg}</h2> </form> + <Link to="/register"> + <button>Create account</button> + </Link> </div> </div> ); diff --git a/src/components/NotificationView.js b/src/components/NotificationView.js new file mode 100644 index 0000000000000000000000000000000000000000..5fcf80141e569562492417d719854bd7b654ced1 --- /dev/null +++ b/src/components/NotificationView.js @@ -0,0 +1,7 @@ +import React from "react"; + +export default class NotificationView extends React.Component { + render() { + return false; + } +} diff --git a/src/components/PlayerlistFaction.js b/src/components/PlayerlistFaction.js new file mode 100644 index 0000000000000000000000000000000000000000..387f67f73b60432ec3f8d0b7ccd435990a6ff3cd --- /dev/null +++ b/src/components/PlayerlistFaction.js @@ -0,0 +1,53 @@ +import React from "react"; +import PlayerlistPlayerCard from "./PlayerlistPlayerCard"; + +export default class PlayerlistFaction extends React.Component { + state = { + factionMembers: null + }; + + // get faction members + componentDidMount() { + this.getFactionMembers(); + } + + getFactionMembers() { + fetch( + `${process.env.REACT_APP_API_URL}/faction/get-faction-members/${ + this.props.faction.factionId + }` + ) + .then(res => res.json()) + .then(res => { + this.setState({ factionMembers: res }); + }) + .catch(error => console.log(error)); + } + + render() { + if (this.state.factionMembers === null) { + return false; + } + + let members = this.state.factionMembers.map(member => { + return ( + <PlayerlistPlayerCard + key={member.gamepersonId} + player={member} + role={this.props.role} + gameId={this.props.gameId} + onChange={() => this.getFactionMembers()} + /> + ); + }); + + return ( + <div> + <br /> + <div>{this.props.faction.factionName}</div> + <br /> + <div>{members}</div> + </div> + ); + } +} diff --git a/src/components/PlayerlistPlayerCard.js b/src/components/PlayerlistPlayerCard.js new file mode 100644 index 0000000000000000000000000000000000000000..ebc2ddeb5c6eb75958cf6390404206f488f73878 --- /dev/null +++ b/src/components/PlayerlistPlayerCard.js @@ -0,0 +1,119 @@ +import React, { Fragment } from "react"; + +export default class PlayerlistPlayerCard extends React.Component { + state = { + edit: false, + roleInput: this.props.player.role + }; + + handleSave = () => { + let token = sessionStorage.getItem("token"); + + if (this.state.roleInput === "") { + return alert("Error: Selected role is not valid"); + } + + if ( + window.confirm( + `Change ${this.props.player.person.name}'s role to "${ + this.state.roleInput + }"?` + ) + ) { + fetch( + `${process.env.REACT_APP_API_URL}/faction/promote/${this.props.gameId}`, + { + method: "PUT", + headers: { + Authorization: "Bearer " + token, + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ + role: this.state.roleInput, + player: this.props.player.gamepersonId + }) + } + ) + .then(res => { + return res.json(); + }) + .then(res => { + this.props.onChange(); + alert( + `Player ${this.props.player.person.name}'s role was changed to "${ + res.role + }"` + ); + this.setState({ edit: false }); + }) + .catch(error => console.log(error)); + } else { + this.setState({ edit: false, roleInput: this.props.player.role }); + } + }; + + render() { + // Normal user view + if (this.props.role !== "admin") { + return ( + <div> + {this.props.player.person.name} : {this.props.player.role} + </div> + ); + } + + // Admin edit view + if (this.state.edit) { + return ( + <div> + {this.props.player.person.name} :{" "} + <select + value={this.state.roleInput} + onChange={e => this.setState({ roleInput: e.target.value })} + > + {roleOptions()} + </select> + <button onClick={this.handleSave}>Save</button> + <button + onClick={() => { + this.setState({ edit: false, roleInput: this.props.player.role }); + }} + > + Cancel + </button> + </div> + ); + } + + // Admin view without editing + return ( + <div> + {this.props.player.person.name} : {this.state.edit && roleOptions()} + {!this.state.edit && this.props.player.role} + {this.props.role === "admin" && !this.state.edit && ( + <button onClick={() => this.setState({ edit: !this.state.edit })}> + Edit + </button> + )} + {this.state.edit && ( + <Fragment> + <button>Save</button> + <button>Cancel</button> + </Fragment> + )} + </div> + ); + } +} + +// Available options for user roles +function roleOptions() { + return ( + <Fragment> + <option value="soldier">Soldier</option> + <option value="factionleader">Faction Leader</option> + <option value="admin">Admin</option> + </Fragment> + ); +} diff --git a/src/components/PlayerlistView.js b/src/components/PlayerlistView.js new file mode 100644 index 0000000000000000000000000000000000000000..6967b0e2e0dc25017ff4940710e3d1e4488a6150 --- /dev/null +++ b/src/components/PlayerlistView.js @@ -0,0 +1,89 @@ +import React from "react"; +import PropTypes from "prop-types"; +import PlayerlistFaction from "./PlayerlistFaction"; + +export default class PlayerlistView extends React.Component { + state = { + factions: null + }; + + componentDidMount() { + // Add event to close the playerlist if "esc" is pressed + document.addEventListener("keyup", this.handleEsc); + + let token = sessionStorage.getItem("token"); + + if (this.props.role !== "soldier" && this.props.role !== "factionleader") { + // get all factions in the game + fetch(`${process.env.REACT_APP_API_URL}/game/${this.props.gameId}`) + .then(res => res.json()) + .then(res => { + this.setState({ factions: res.factions }); + }) + .catch(error => console.log(error)); + } else { + // get player's faction + fetch( + `${process.env.REACT_APP_API_URL}/faction/check-faction/${ + this.props.gameId + }`, + { + method: "GET", + headers: { + Authorization: "Bearer " + token + } + } + ) + .then(res => res.json()) + .then(res => { + this.setState({ factions: [res] }); + }) + .catch(error => console.log(error)); + } + } + + // remove view with "ESC" + handleEsc = e => { + if (e.keyCode === 27) { + this.props.toggleView(); + } + }; + + componentWillUnmount() { + document.removeEventListener("keyup", this.handleEsc); + } + + render() { + if (this.state.factions === null) { + return false; + } + + let factionlistItems = this.state.factions.map(faction => ( + <PlayerlistFaction + key={faction.factionId} + faction={faction} + role={this.props.role} + gameId={this.props.gameId} + /> + )); + + return ( + <div className="fade-main"> + <div className="sticky"> + <span + id="closeEditGameFormX" + className="close" + onClick={() => this.props.toggleView()} + > + × + </span> + </div> + {factionlistItems} + </div> + ); + } +} + +PlayerlistView.propTypes = { + gameId: PropTypes.string +}; diff --git a/src/components/RegisterForm.js b/src/components/RegisterForm.js index 8c40fd59c58554dfa5604633fe670bd580c51053..889f3351917fd7d4366fa0afde5ed3b3f348ae68 100644 --- a/src/components/RegisterForm.js +++ b/src/components/RegisterForm.js @@ -1,4 +1,5 @@ import React from "react"; +import { Link } from "react-router-dom"; export class RegisterForm extends React.Component { constructor(props) { @@ -23,18 +24,6 @@ export class RegisterForm extends React.Component { this.setState({ [name]: value }); }; - // show/hide this form - handleView = e => { - this.props.toggleView(this.props.view); - }; - - // remove register view with ESC - handleEsc = e => { - if (e.keyCode === 27) { - this.handleView(); - } - }; - handleRegister = e => { const name = this.state.username; const password = this.state.password; @@ -60,7 +49,6 @@ export class RegisterForm extends React.Component { result => { if (result.name) { this.props.handleState(result); - this.handleView(); } else { this.handleError(result.message); } @@ -75,25 +63,13 @@ export class RegisterForm extends React.Component { } }; - componentDidMount() { - document.addEventListener("keyup", this.handleEsc); - } - - componentWillUnmount() { - document.removeEventListener("keyup", this.handleEsc); - } // UNCOMMENT "REQUIRED" FOR PRODUCTION render() { return ( <div className="fade-main"> - <div className="sticky"> - <span className="close" onClick={this.handleView}> - × - </span> - </div> <div className="login"> <form onSubmit={this.handleRegister}> - <h1>register new user</h1> + <h1>Register</h1> <br /> <input placeholder="Enter Username" @@ -122,9 +98,12 @@ export class RegisterForm extends React.Component { //required /> <br /> - <button type="submit">register</button> + <button type="submit">Submit</button> <h2>{this.state.errorMsg}</h2> </form> + <Link to="/"> + <button>Login</button> + </Link> </div> </div> ); diff --git a/src/components/TaskItem.js b/src/components/TaskItem.js index 620ba85b27a310ef6a284cfd563e88695d5cfa0a..b9dca50947d36503a5457c9310546180bb8769fe 100644 --- a/src/components/TaskItem.js +++ b/src/components/TaskItem.js @@ -21,7 +21,7 @@ class TaskItem extends React.Component { }; getFactionlist(gameId) { - fetch(`${process.env.REACT_APP_URL}/game/${gameId}`, { + fetch(`${process.env.REACT_APP_API_URL}/game/${gameId}`, { method: "GET" }) .then(result => { @@ -56,10 +56,17 @@ class TaskItem extends React.Component { onTaskDelete = e => { e.preventDefault(); - this.props.onDelete(this.props.task.taskId); - this.setState({ - edit: false - }); + + if ( + window.confirm( + `Are you sure you want to delete task "${this.props.task.taskName}"` + ) + ) { + this.props.onDelete(this.props.task.taskId); + this.setState({ + edit: false + }); + } }; render() { @@ -92,7 +99,7 @@ class TaskItem extends React.Component { <label>Winner: {this.props.task.taskWinner.factionName}</label> )} </div> - {this.props.task.taskIsActive && ( + {this.props.task.taskIsActive && this.props.role === "admin" && ( <button onClick={this.onEditClick}>Edit</button> )} {this.state.edit && ( @@ -108,9 +115,14 @@ class TaskItem extends React.Component { <button type="submit">Save</button> </form> )} - <button onClick={this.onTaskDelete} style={{ backgroundColor: "red" }}> - Delete - </button> + {this.props.role === "admin" && ( + <button + onClick={this.onTaskDelete} + style={{ backgroundColor: "red" }} + > + Delete + </button> + )} </div> ); } diff --git a/src/components/TaskList.js b/src/components/TaskList.js index fedbc03ae0dbac449f8aa4c7518dcca03597b897..ce08afc64a2f4ccfab6d1d77cd0f8594e1cce963 100644 --- a/src/components/TaskList.js +++ b/src/components/TaskList.js @@ -21,7 +21,7 @@ class TaskList extends React.Component { getTasks(gameId) { let token = sessionStorage.getItem("token"); - fetch(`${process.env.REACT_APP_URL}/task/get-tasks/${gameId}`, { + fetch(`${process.env.REACT_APP_API_URL}/task/get-tasks/${gameId}`, { method: "GET", headers: { Authorization: "Bearer " + token @@ -43,7 +43,7 @@ class TaskList extends React.Component { } getFactionlist(gameId) { - fetch(`${process.env.REACT_APP_URL}/game/${gameId}`, { + fetch(`${process.env.REACT_APP_API_URL}/game/${gameId}`, { method: "GET" }) .then(result => { @@ -68,24 +68,27 @@ class TaskList extends React.Component { } let token = sessionStorage.getItem("token"); - fetch(`${process.env.REACT_APP_URL}/task/new-task/${this.props.gameId}`, { - method: "POST", - headers: { - Authorization: "Bearer " + token, - "Content-Type": "application/json" - }, - body: JSON.stringify({ - taskName: this.state.taskNameInput, - taskDescription: this.state.taskDescriptionInput, - taskIsActive: true, - faction: - this.state.selectedFactionId === "" - ? null - : this.state.selectedFactionId, - taskWinner: null, - taskGame: this.props.gameId - }) - }) + fetch( + `${process.env.REACT_APP_API_URL}/task/new-task/${this.props.gameId}`, + { + method: "POST", + headers: { + Authorization: "Bearer " + token, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + taskName: this.state.taskNameInput, + taskDescription: this.state.taskDescriptionInput, + taskIsActive: true, + faction: + this.state.selectedFactionId === "" + ? null + : this.state.selectedFactionId, + taskWinner: null, + taskGame: this.props.gameId + }) + } + ) .then(result => { if (!result.ok) { throw Error(Response.statusText); @@ -112,18 +115,21 @@ class TaskList extends React.Component { onTaskEditSave = (task, winnerFactionId) => { let token = sessionStorage.getItem("token"); - fetch(`${process.env.REACT_APP_URL}/task/edit-task/${this.props.gameId}`, { - method: "POST", - headers: { - Authorization: "Bearer " + token, - "Content-Type": "application/json" - }, - body: JSON.stringify({ - taskId: task.taskId, - taskWinner: winnerFactionId, - taskGame: this.props.gameId - }) - }) + fetch( + `${process.env.REACT_APP_API_URL}/task/edit-task/${this.props.gameId}`, + { + method: "POST", + headers: { + Authorization: "Bearer " + token, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + taskId: task.taskId, + taskWinner: winnerFactionId, + taskGame: this.props.gameId + }) + } + ) .then(result => { if (!result.ok) { throw Error(result.responseText); @@ -144,7 +150,7 @@ class TaskList extends React.Component { } let token = sessionStorage.getItem("token"); fetch( - `${process.env.REACT_APP_URL}/task/delete-task/${this.props.gameId}`, + `${process.env.REACT_APP_API_URL}/task/delete-task/${this.props.gameId}`, { method: "DELETE", headers: { @@ -180,6 +186,7 @@ class TaskList extends React.Component { <TaskItem key={task.taskId} task={task} + role={this.props.role} gameId={this.props.gameId} onSave={this.onTaskEditSave} onDelete={this.onTaskDeletion} @@ -190,6 +197,7 @@ class TaskList extends React.Component { <TaskItem key={task.taskId} task={task} + role={this.props.role} gameId={this.props.gameId} onSave={this.onTaskEditSave} onDelete={this.onTaskDeletion} @@ -216,31 +224,33 @@ class TaskList extends React.Component { return ReactDOM.createPortal( <div className="tasklist"> <h1>Tasklist</h1> - <form className="task-form" onSubmit={this.handleTaskCreation}> - <label>New task</label> - <input - id="taskNameInput" - type="text" - placeholder="Task name" - minLength="3" - value={this.state.taskNameInput} - onChange={e => this.setState({ taskNameInput: e.target.value })} - /> - <textarea - id="taskDescriptionInput" - placeholder="Task description" - value={this.state.taskDescriptionInput} - onChange={e => - this.setState({ taskDescriptionInput: e.target.value }) - } - /> - <select id="taskFactionSelect" onChange={this.handleFactionChange}> - {factionlistItems} - </select> - <button id="newTaskSubmitButton" type="submit"> - Add new task - </button> - </form> + {this.props.role === "admin" && ( + <form className="task-form" onSubmit={this.handleTaskCreation}> + <label>New task</label> + <input + id="taskNameInput" + type="text" + placeholder="Task name" + minLength="3" + value={this.state.taskNameInput} + onChange={e => this.setState({ taskNameInput: e.target.value })} + /> + <textarea + id="taskDescriptionInput" + placeholder="Task description" + value={this.state.taskDescriptionInput} + onChange={e => + this.setState({ taskDescriptionInput: e.target.value }) + } + /> + <select id="taskFactionSelect" onChange={this.handleFactionChange}> + {factionlistItems} + </select> + <button id="newTaskSubmitButton" type="submit"> + Add new task + </button> + </form> + )} {incompleteTasks} <br /> <label>Completed tasks</label> diff --git a/src/components/TaskListButton.js b/src/components/TaskListButton.js index 988beab9c3b21520190f5fde33774e76ac74ba35..c0e5f8c1257edaaae2e062fbc8dce562538aa5c1 100644 --- a/src/components/TaskListButton.js +++ b/src/components/TaskListButton.js @@ -1,12 +1,12 @@ import React, { Fragment } from "react"; import TaskList from "./TaskList"; -class TaskListButton extends React.Component { +export default class TaskListButton extends React.Component { constructor(props) { super(props); this.state = { - open: false, - newTasks: 0 + open: false + // newTasks: 0 }; this.handleClick = this.handleClick.bind(this); @@ -28,10 +28,11 @@ class TaskListButton extends React.Component { open: !this.state.open }, () => { + // Websocket task notification template // Set new task cout to zero when the tasklist opens - if (this.state.open) { - this.setState({ newTasks: 0 }); - } + // if (this.state.open) { + // this.setState({ newTasks: 0 }); + // } } ); }; @@ -40,14 +41,13 @@ class TaskListButton extends React.Component { return ( <Fragment> <button id="tasklistButton" onClick={this.handleClick}> - Tasks ({this.state.newTasks}) + {/* Tasks ({this.state.newTasks}) */} + Tasks </button> {this.state.open && ( - <TaskList gameId="2c097e6a-591c-4a27-b7cb-38eb44e1f31c" /> + <TaskList gameId={this.props.gameId} role={this.props.role} /> )} </Fragment> ); } } - -export default TaskListButton;