diff --git a/.env b/.env new file mode 100644 index 0000000000000000000000000000000000000000..191b674a09d3c64e304abb26cfc22b19a8f660d4 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +REACT_APP_API_URL = "http://localhost:5000" \ No newline at end of file diff --git a/public/index.html b/public/index.html index 8b2f9d1cb04b63987cab6a5b0645beb0aeafff57..421a898f120b5f1f39e1b60e6f6db3ef91e754f1 100644 --- a/public/index.html +++ b/public/index.html @@ -23,6 +23,7 @@ </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> + <div id="tasklist"></div> <div id="root"></div> <div id="form"></div> <!-- diff --git a/src/App.css b/src/App.css index 7d7f9508468c0da993d1991880bbf8fde6c93677..bccff582ded131e5f820cb21d76930d2f271641d 100644 --- a/src/App.css +++ b/src/App.css @@ -57,6 +57,8 @@ div.fade-main { margin: auto; text-align: center; background-color: rgba(0, 0, 0, 0.85); + color: white; + overflow: scroll; } div.sticky { @@ -197,3 +199,18 @@ div.login button:hover { text-shadow: none; font-weight: normal; } +.tasklist { + position: absolute; + margin-top: 50px; + z-index: 2020; + background-color: #ffffff80; +} + +.tasklist-item { + margin: 20px; +} + +.task-form { + display: flex; + flex-direction: column; +} diff --git a/src/components/EditGameForm.js b/src/components/EditGameForm.js index fbc37f8995faaaec41f56c51a6b1b6cdd6ba5d4b..68773d82fadcf47553f18653a44973d40438f393 100644 --- a/src/components/EditGameForm.js +++ b/src/components/EditGameForm.js @@ -1,4 +1,4 @@ -import React from "react"; +import React, { Fragment } from "react"; import ReactDOM from "react-dom"; import { Map, TileLayer } from "react-leaflet"; @@ -7,17 +7,26 @@ export class EditGameForm extends React.Component { super(props); this.state = { + zoom: 13, gamename: "", description: "", startDate: "", startTime: "", endDate: "", endTime: "", - zoom: 13, + map: "", mapCenter: { lat: 62.2416479, lng: 25.7597186 - } + }, + factionNameInput: "", // >= 2 chars + factionPasswordInput: "", // >= 3 chars + factions: [], + objectivePointDescriptionInput: "", // >= 7 + objectivePointMultiplierInput: "", // number + objectivePoints: [], + capture_time: 300, + confirmation_time: 60 }; this.handleMapDrag = this.handleMapDrag.bind(this); @@ -32,6 +41,142 @@ export class EditGameForm extends React.Component { this.setState({ [name]: value }); }; + handleFactionAdd = e => { + e.preventDefault(); + + if ( + this.state.factionNameInput === "" || + this.state.factionPasswordInput === "" + ) { + return alert("Faction needs a name and password"); + } + + if ( + this.state.factions.findIndex( + faction => faction.factionName === this.state.factionNameInput + ) !== -1 + ) { + return alert( + "Faction " + this.state.factionNameInput + " already exists" + ); + } + + this.setState(state => { + let factions = state.factions; + factions.push({ + factionName: this.state.factionNameInput, + factionPassword: this.state.factionPasswordInput, + multiplier: 1 + }); + return { + factions: factions, + factionNameInput: "", + factionPasswordInput: "" + }; + }); + }; + + removeFaction(factionName) { + this.setState(state => { + let factions = state.factions; + const index = factions.findIndex( + faction => faction.factionName === factionName + ); + + if (index !== -1) { + factions.splice(index, 1); + } else { + console.log("Faction is not in the faction list"); + } + + return factions; + }); + } + + handleObjectivePointAdd = e => { + e.preventDefault(); + + if ( + this.state.objectivePointDescriptionInput === "" || + this.state.objectivePointMultiplierInput === "" + ) { + return alert("Capture point needs an ID and multiplier"); + } + + if ( + this.state.objectivePoints.findIndex( + point => + point.objectivePointDescription === + this.state.objectivePointDescriptionInput + ) !== -1 + ) { + return alert( + "Capture point " + + this.state.objectivePointDescriptionInput + + " already exists" + ); + } + + this.setState(state => { + let objectivePoints = state.objectivePoints; + objectivePoints.push({ + objectivePointDescription: this.state.objectivePointDescriptionInput, + objectivePointMultiplier: parseFloat( + this.state.objectivePointMultiplierInput + ) + }); + + return { + objectivePoints: objectivePoints, + objectivePointDescriptionInput: "", + objectivePointMultiplierInput: "" + }; + }); + }; + + removeObjectivePoint(objectivePointId) { + this.setState(state => { + let objectivePoints = state.objectivePoints; + const index = objectivePoints.findIndex( + point => point.objectivePointDescription === objectivePointId + ); + + if (index !== -1) { + objectivePoints.splice(index, 1); + } else { + console.log("Objective point is not in the point list"); + } + + return objectivePoints; + }); + } + + handleGameDeletion = e => { + e.preventDefault(); + + let token = sessionStorage.getItem("token"); + + if (window.confirm("Are you sure you want to delete this game")) { + alert("Game deleted"); + + fetch( + `${process.env.REACT_APP_API_URL}/game/delete/${this.props.gameId}`, + { + method: "DELETE", + headers: { + Authorization: "Bearer " + token + } + } + ) + .then(result => result.json()) + .then(result => { + console.log(result); + this.handleView(); + }) + .catch(error => console.log(error)); + } + }; + // show/hide this form handleView = e => { this.props.toggleView(this.props.view); @@ -57,20 +202,44 @@ export class EditGameForm extends React.Component { }; handleGameSave = e => { + e.preventDefault(); + let startDate = this.state.startDate + "T" + this.state.startTime + ":00.000Z"; let endDate = this.state.endDate + "T" + this.state.endTime + ":00.000Z"; - const gameObject = { + let objectivePoints = this.state.objectivePoints; + + // objective points are not allowed if the game has no factions + if (this.state.factions.length === 0) { + objectivePoints = []; + } + + // Object the form sends to server + let gameObject = { name: this.state.gamename, desc: this.state.description, map: "", startdate: startDate, enddate: endDate, - center: this.state.mapCenter + center: this.state.mapCenter, + factions: this.state.factions, + objective_points: objectivePoints }; - e.preventDefault(); + // Add node settings to the game if the game has objective points + if (objectivePoints.length > 0) { + gameObject.nodesettings = { + node_settings: { + capture_time: this.state.capture_time, + confirmation_time: this.state.confirmation_time, + owner: 0, + capture: 0, + buttons_available: 16, + heartbeat_interval: 60 + } + }; + } let token = sessionStorage.getItem("token"); @@ -87,7 +256,9 @@ export class EditGameForm extends React.Component { .then(res => res.json()) .then(result => { alert(result.message); - this.handleView(); + if (result.code === 200) { + this.handleView(); + } }) .catch(error => console.log("Error: ", error)); }; @@ -104,27 +275,113 @@ export class EditGameForm extends React.Component { getGameInfo(gameId) { fetch(`${process.env.REACT_APP_API_URL}/game/${gameId}`) .then(response => response.json()) - .then(json => this.handleGameInfo(json)) + .then(json => this.setGameInfoToState(json)) .catch(error => console.log(error)); } - handleGameInfo(json) { - this.setState({ - gamename: json.name, - description: json.desc, - startDate: json.startdate.substring(0, 10), - startTime: json.startdate.substring(11, 16), - endDate: json.enddate.substring(0, 10), - endTime: json.enddate.substring(11, 16), - zoom: 13, - mapCenter: { - lat: json.center.lat, - lng: json.center.lng + setGameInfoToState(json) { + let token = sessionStorage.getItem("token"); + + // Get factions and passwordds + fetch( + `${process.env.REACT_APP_API_URL}/game/get-factions/${this.props.gameId}`, + { + method: "GET", + headers: { + Authorization: "Bearer " + token + } } - }); + ) + .then(result => result.json()) + .then(result => { + let factions = result.map(faction => { + return { + factionName: faction.factionName, + factionPassword: faction.factionPassword, + multiplier: 1 + }; + }); + + // Remove objective point's id from the object + let objectivePoints = json.objective_points.map(point => { + return { + objectivePointDescription: point.objectivePointDescription, + objectivePointMultiplier: point.objectivePointMultiplier + }; + }); + + // get node settings if the settings exists in the game + let nodesettings = + json.nodesettings !== null && + json.nodesettings.node_settings !== undefined + ? json.nodesettings.node_settings + : undefined; + + this.setState({ + gamename: json.name, + description: json.desc, + startDate: json.startdate.substring(0, 10), + startTime: json.startdate.substring(11, 16), + endDate: json.enddate.substring(0, 10), + endTime: json.enddate.substring(11, 16), + mapCenter: { + lat: json.center.lat, + lng: json.center.lng + }, + factions: factions, + objectivePoints: objectivePoints, + capture_time: + nodesettings !== undefined + ? json.nodesettings.node_settings.capture_time + : this.state.capture_time, + confirmation_time: + nodesettings !== undefined + ? json.nodesettings.node_settings.confirmation_time + : this.state.confirmation_time + }); + }) + .catch(error => console.log(error)); } render() { + let factions = []; + for (let i = 0; i < this.state.factions.length; i++) { + const faction = this.state.factions[i]; + factions.push( + <li key={faction.factionName}> + <div> + {faction.factionName} : {faction.factionPassword} + </div> + <button + type="button" + onClick={() => this.removeFaction(faction.factionName)} + > + Remove + </button> + </li> + ); + } + + let objectivePoints = []; + for (let i = 0; i < this.state.objectivePoints.length; i++) { + const point = this.state.objectivePoints[i]; + objectivePoints.push( + <li key={point.objectivePointDescription}> + <div> + {point.objectivePointDescription} : {point.objectivePointMultiplier} + </div> + <button + type="button" + onClick={() => + this.removeObjectivePoint(point.objectivePointDescription) + } + > + Remove + </button> + </li> + ); + } + return ReactDOM.createPortal( <div className="fade-main"> <div className="sticky"> @@ -137,85 +394,185 @@ export class EditGameForm extends React.Component { </span> </div> <div className=""> - <form onSubmit={this.handleGameSave}> - <h1>Demo Game Creation</h1> - <br /> - <input - placeholder="Game name" - name="gamename" - value={this.state.gamename} - onChange={this.handleChange} - id="editGameNameInput" - /> - <br /> - <input - placeholder="Description" - type="text" - name="description" - value={this.state.description} - onChange={this.handleChange} - id="editGameDescriptionInput" - /> - <br /> - <label className="">Start:</label> - <input - className="formDate" - type="date" - name="startDate" - value={this.state.startDate} - onChange={this.handleChange} - id="editGameDateStartInput" - /> - <input - className="formTime" - type="time" - name="startTime" - value={this.state.startTime} - onChange={this.handleChange} - rid="editGameTimeStartInput" - /> - <br /> - <label className="">End:</label> - <input - className="formDate" - type="date" - name="endDate" - value={this.state.endDate} - onChange={this.handleChange} - min={this.state.startDate} - id="editGameDateEndInput" - /> - <input - className="formTime" - type="time" - name="endTime" - value={this.state.endTime} - onChange={this.handleChange} - id="editGameTimeEndInput" + <form id="gameEditForm" onSubmit={this.handleGameSave} /> + <form id="factionAddFrom" onSubmit={this.handleFactionAdd} /> + <form id="gameDeletionForm" onSubmit={this.handleGameDeletion} /> + <form + id="objectivePointAddFrom" + onSubmit={this.handleObjectivePointAdd} + /> + + <h1>Demo Game Editor</h1> + <br /> + <input + placeholder="Game name" + name="gamename" + value={this.state.gamename} + onChange={this.handleChange} + id="editGameNameInput" + form="gameEditForm" + required + /> + <br /> + <input + placeholder="Description" + type="text" + name="description" + value={this.state.description} + onChange={this.handleChange} + id="editGameDescriptionInput" + form="gameEditForm" + required + /> + <br /> + <label className="">Start:</label> + <input + className="formDate" + type="date" + name="startDate" + value={this.state.startDate} + onChange={this.handleChange} + id="editGameDateStartInput" + form="gameEditForm" + required + /> + <input + className="formTime" + type="time" + name="startTime" + value={this.state.startTime} + onChange={this.handleChange} + sid="editGameTimeStartInput" + form="gameEditForm" + required + /> + <br /> + <label className="">End:</label> + <input + className="formDate" + type="date" + name="endDate" + value={this.state.endDate} + onChange={this.handleChange} + min={this.state.startDate} + id="editGameDateEndInput" + form="gameEditForm" + required + /> + <input + className="formTime" + type="time" + name="endTime" + value={this.state.endTime} + onChange={this.handleChange} + id="editGameTimeEndInput" + form="gameEditForm" + required + /> + <br /> + <br /> + + <label>Factions</label> + <br /> + <input + name="factionNameInput" + value={this.state.factionNameInput} + minLength="2" + onChange={this.handleChange} + placeholder="Add new faction" + form="factionAddFrom" + /> + <input + name="factionPasswordInput" + value={this.state.factionPasswordInput} + minLength="3" + onChange={this.handleChange} + placeholder="Faction password" + form="factionAddFrom" + /> + <button type="submit" form="factionAddFrom"> + Add + </button> + <ul>{factions}</ul> + <br /> + <br /> + <label>Objective points</label> + <br /> + <input + name="objectivePointDescriptionInput" + type="number" + value={this.state.objectivePointDescriptionInput} + onChange={this.handleChange} + placeholder="Objective point id" + min="1000000" + form="objectivePointAddFrom" + /> + <input + name="objectivePointMultiplierInput" + type="number" + value={this.state.objectivePointMultiplierInput} + onChange={this.handleChange} + placeholder="Objective point multiplier" + form="objectivePointAddFrom" + /> + <button type="submit" form="objectivePointAddFrom"> + Add + </button> + <ul>{objectivePoints}</ul> + <br /> + <br /> + <label>Node things (set if objective points are in the game)</label> + <br /> + <br /> + <label className="" form="gameEditForm"> + Capture time: + </label> + <input + name="capture_time" + type="number" + value={this.state.capture_time} + form="gameEditForm" + onChange={this.handleChange} + /> + <label className="">Confimation time:</label> + <input + name="confirmation_time" + type="number" + value={this.state.confirmation_time} + form="gameEditForm" + onChange={this.handleChange} + /> + <br /> + <br /> + <label>Map things</label> + <br /> + <Map + id="editGameCenterMap" + className="" + center={[this.state.mapCenter.lat, this.state.mapCenter.lng]} + zoom={this.state.zoom} + maxZoom="13" + style={{ height: "400px", width: "400px" }} + onmoveend={this.handleMapDrag} + onzoomend={this.handleMapScroll} + > + <TileLayer + attribution="Maanmittauslaitoksen kartta" + url=" https://tiles.kartat.kapsi.fi/taustakartta/{z}/{x}/{y}.jpg" /> - <br /> - <label>Map things</label> - <br /> - <Map - id="editGameCenterMap" - className="" - center={[this.state.mapCenter.lat, this.state.mapCenter.lng]} - zoom={this.state.zoom} - style={{ height: "400px", width: "400px" }} - onmoveend={this.handleMapDrag} - onzoomend={this.handleMapScroll} - > - <TileLayer - attribution="Maanmittauslaitoksen kartta" - url=" https://tiles.kartat.kapsi.fi/taustakartta/{z}/{x}/{y}.jpg" - /> - </Map> - <br /> - <button id="editGameSubmitButton" type="submit"> - Save changes - </button> - <h2>{this.state.errorMsg}</h2> - </form> + </Map> + <br /> + <button + style={{ backgroundColor: "red" }} + type="submit" + form="gameDeletionForm" + > + Delete + </button> + <button id="editGameSubmitButton" type="submit" form="gameEditForm"> + Save changes + </button> + <h2>{this.state.errorMsg}</h2> </div> </div>, document.getElementById("form") diff --git a/src/components/GameList.js b/src/components/GameList.js index f3fc6d3c71a350b960302f428ed66df3271a8e87..3a3724d68893ad8a1266fdb3f2391b11fdb6c27f 100644 --- a/src/components/GameList.js +++ b/src/components/GameList.js @@ -1,13 +1,15 @@ import React, { Fragment } from "react"; import EditGameForm from "./EditGameForm"; +import JoinGameForm from "./JoinGameForm"; class GameList extends React.Component { constructor(props) { super(props); this.state = { games: [], - selectedGame: null, - editForm: false + selectedGame: undefined, + editForm: false, + joinForm: false }; this.toggleView = this.toggleView.bind(this); @@ -21,9 +23,18 @@ class GameList extends React.Component { fetch(`${process.env.REACT_APP_API_URL}/game/listgames`) .then(response => response.json()) .then(games => { + let selectedGame = + this.state.selectedGame !== undefined + ? this.state.selectedGame + : undefined; this.setState({ games: games, - selectedGame: games !== undefined && games[0].id + selectedGame: + selectedGame !== undefined + ? selectedGame + : games !== undefined + ? games[0].id + : undefined }); // taking the initialized gameID to UserMap.js (GameList.js -> Header.js -> App.js -> UserMap.js) this.props.handleGameChange(games[0].id); @@ -46,7 +57,7 @@ class GameList extends React.Component { }; handleEditClick = e => { - if (this.state.selectedGame === null) { + if (this.state.selectedGame === undefined) { alert("No game selected"); } else { this.setState({ @@ -55,6 +66,17 @@ class GameList extends React.Component { } }; + handleJoinClick = e => { + if (this.state.selectedGame === undefined) { + alert("No game selected"); + } else { + this.setState({ + joinForm: true, + editForm: false + }); + } + }; + toggleView = e => { this.setState({ editForm: !this.state.editForm @@ -81,16 +103,27 @@ class GameList extends React.Component { {items} </select> {sessionStorage.getItem("token") && ( - <button id="editGameButton" onClick={this.handleEditClick}> - Edit game - </button> + <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 !== null && ( + {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} + /> + )} </Fragment> ); } diff --git a/src/components/GameSidebar.js b/src/components/GameSidebar.js new file mode 100644 index 0000000000000000000000000000000000000000..0511cdc3e9762a1e027a7f180ff8112289e5d311 --- /dev/null +++ b/src/components/GameSidebar.js @@ -0,0 +1,31 @@ +import React from "react"; +import NewGameForm from "./NewGameForm"; +import GameList from "./GameList"; + +export default class GameSidebar extends React.Component { + state = { + form: "" + }; + + toggleView = view => { + this.setState({ + form: view + }); + }; + + render() { + return ( + <div className="game-sidebar"> + <GameList /> + {this.props.loggedIn && ( + <button id="newGameButton" onClick={() => this.toggleView("newgame")}> + New Game + </button> + )} + {this.state.form === "newgame" && ( + <NewGameForm view="" toggleView={() => this.toggleView("")} /> + )} + </div> + ); + } +} diff --git a/src/components/Header.js b/src/components/Header.js index 0663cc382019e1aa00f9757a98b4fe5fb1a5508f..d83e7aad5577a9a9a8537e8b11a2c8a4a93b929c 100644 --- a/src/components/Header.js +++ b/src/components/Header.js @@ -1,9 +1,9 @@ -import React from 'react'; +import React from "react"; -import LoginForm from './LoginForm'; -import RegisterForm from './RegisterForm'; -import GameList from './GameList'; -import NewGameForm from './NewGameForm'; +import LoginForm from "./LoginForm"; +import RegisterForm from "./RegisterForm"; +import TaskListButton from "./TaskListButton"; +import GameSidebar from "./GameSidebar"; class Header extends React.Component { constructor(props) { @@ -11,9 +11,10 @@ class Header extends React.Component { } state = { - form: '', // Popup form (login, register etc.) + form: "", // Popup form (login, register etc.) username: null, - token: null + token: null, + sidebar: false }; // toggles the login/register view @@ -24,23 +25,23 @@ class Header extends React.Component { }; handleState = data => { - sessionStorage.setItem('name', data.name); - sessionStorage.setItem('token', data.token); + sessionStorage.setItem("name", data.name); + sessionStorage.setItem("token", data.token); this.setState({ username: data.name, token: data.token }); }; handleLogout = () => { this.setState({ username: null, token: null }); - sessionStorage.removeItem('token'); + sessionStorage.removeItem("token"); }; // verifies the token (if it exists) on element mount with backend server componentDidMount() { - let token = sessionStorage.getItem('token'); + let token = sessionStorage.getItem("token"); if (token) { fetch(`${process.env.REACT_APP_API_URL}/user/verify`, { headers: { - Authorization: 'Bearer ' + token + Authorization: "Bearer " + token } }) .then(res => res.json()) @@ -49,7 +50,7 @@ class Header extends React.Component { // if token is still valid, login user if (result === true) { this.setState({ - username: sessionStorage.getItem('name'), + username: sessionStorage.getItem("name"), token: token }); // logout user if token has expired / is invalid @@ -67,56 +68,51 @@ class Header extends React.Component { render() { return ( <div> - <div className='header'> + <div className="header"> {!this.state.username && ( <button - id='registerButton' - onClick={() => this.toggleView('register')} + id="registerButton" + onClick={() => this.toggleView("register")} > register </button> )} {!this.state.username && ( - <button id='loginButton' onClick={() => this.toggleView('login')}> + <button id="loginButton" onClick={() => this.toggleView("login")}> login </button> )} + {this.state.username && ( - <button - id='newGameButton' - onClick={() => this.toggleView('newgame')} - > - New Game - </button> - )} - {this.state.username && ( - <button id='logoutButton' onClick={this.handleLogout}> + <button id="logoutButton" onClick={this.handleLogout}> logout </button> )} {this.state.username && <button>{this.state.username}</button>} - <button id='changeLayerButton' onClick={this.props.handleLayerChange}> + <button id="changeLayerButton" onClick={this.props.handleLayerChange}> change layer </button> - <GameList handleGameChange={this.props.handleGameChange} /> + {this.state.username && <TaskListButton />} + <button + id="sidebarButton" + onClick={() => this.setState({ sidebar: !this.state.sidebar })} + > + Tools + </button> + {this.state.sidebar && ( + <GameSidebar loggedIn={this.state.username ? true : false} /> + )} </div> - {this.state.form === 'register' && ( + {this.state.form === "register" && ( <RegisterForm - view='' + view="" handleState={this.handleState} toggleView={this.toggleView} /> )} - {this.state.form === 'login' && ( + {this.state.form === "login" && ( <LoginForm - view='' - handleState={this.handleState} - toggleView={this.toggleView} - /> - )} - {this.state.form === 'newgame' && ( - <NewGameForm - view='' + view="" handleState={this.handleState} toggleView={this.toggleView} /> diff --git a/src/components/JoinGameForm.js b/src/components/JoinGameForm.js new file mode 100644 index 0000000000000000000000000000000000000000..7ea0aaf958683a807ee73ed23abe19882f591fe7 --- /dev/null +++ b/src/components/JoinGameForm.js @@ -0,0 +1,41 @@ +import React from "react"; + +export default class JoinGameForm extends React.Component { + constructor(props) { + super(props); + this.state = { + gameJSON: undefined + }; + } + + componentDidMount() { + if (this.props.gameId === undefined) { + alert("game not selected"); + } else { + fetch(`${process.env.REACT_APP_API_URL}/game/${this.props.gameId}`) + .then(result => result.json()) + .then(json => { + this.setState({ + gameJSON: json + }); + }) + .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> + ); + } +} diff --git a/src/components/LoginForm.js b/src/components/LoginForm.js index 61f3dec0a63cc4391c5f7a328303e9db9513d726..dc993717505f6ce237391bd05d892b2c63117110 100644 --- a/src/components/LoginForm.js +++ b/src/components/LoginForm.js @@ -98,7 +98,7 @@ export class LoginForm extends React.Component { value={this.state.username} onChange={this.handleChange} id="loginUsernameInput" - autofocus + autoFocus /> <br /> <input diff --git a/src/components/NewGameForm.js b/src/components/NewGameForm.js index e84940d00c6619d3793d5b9cc5746ebf3269e36c..778373289f9a0980b688469baa3e05f409d4c59e 100644 --- a/src/components/NewGameForm.js +++ b/src/components/NewGameForm.js @@ -86,7 +86,12 @@ export class NewGameForm extends React.Component { }) .then(res => res.json()) .then(result => { - this.handleView(); + if (result.code) { + alert(result.message); + } else { + alert(`Game ${this.state.gamename} added`); + this.handleView(); + } }) .catch(error => console.log("Error: ", error)); }; @@ -112,83 +117,98 @@ export class NewGameForm extends React.Component { </span> </div> <div className=""> - <form onSubmit={this.handleGameCreation}> - <h1>Demo Game Creation</h1> - <br /> - <input - placeholder="Game name" - name="gamename" - value={this.state.gamename} - onChange={this.handleChange} - id="newGameNameInput" - /> - <br /> - <input - placeholder="Description" - type="text" - name="description" - value={this.state.description} - onChange={this.handleChange} - id="newGameDescriptionInput" - /> - <br /> - <label className="">Start:</label> - <input - className="formDate" - type="date" - name="startDate" - value={this.state.startDate} - onChange={this.handleChange} - id="newGameDateStartInput" - /> - <input - className="formTime" - type="time" - name="startTime" - onChange={this.handleChange} - id="newGameTimeStartInput" - /> - <br /> - <label className="">End:</label> - <input - className="formDate" - type="date" - name="endDate" - value={this.state.endDate} - onChange={this.handleChange} - min={this.state.startDate} - id="newGameDateEndInput" - /> - <input - className="formTime" - type="time" - name="endTime" - onChange={this.handleChange} - id="newGameTimeEndInput" + <form id="gameCreationForm" onSubmit={this.handleGameCreation} /> + <h1>Demo Game Creation</h1> + <br /> + <input + placeholder="Game name" + name="gamename" + value={this.state.gamename} + onChange={this.handleChange} + id="newGameNameInput" + form="gameCreationForm" + required + /> + <br /> + <input + placeholder="Description" + type="text" + name="description" + value={this.state.description} + onChange={this.handleChange} + id="newGameDescriptionInput" + form="gameCreationForm" + required + /> + <br /> + <label className="">Start:</label> + <input + className="formDate" + type="date" + name="startDate" + value={this.state.startDate} + onChange={this.handleChange} + id="newGameDateStartInput" + form="gameCreationForm" + required + /> + <input + className="formTime" + type="time" + name="startTime" + onChange={this.handleChange} + id="newGameTimeStartInput" + form="gameCreationForm" + required + /> + <br /> + <label className="">End:</label> + <input + className="formDate" + type="date" + name="endDate" + value={this.state.endDate} + onChange={this.handleChange} + min={this.state.startDate} + id="newGameDateEndInput" + form="gameCreationForm" + required + /> + <input + className="formTime" + type="time" + name="endTime" + onChange={this.handleChange} + id="newGameTimeEndInput" + form="gameCreationForm" + required + /> + <br /> + <label>Map things</label> + <br /> + <Map + id="newGameCenterMap" + className="" + center={[this.state.mapCenter.lat, this.state.mapCenter.lng]} + zoom={this.state.zoom} + style={{ height: "400px", width: "400px" }} + onmoveend={this.handleMapDrag} + onzoomend={this.handleMapScroll} + > + <TileLayer + attribution="Maanmittauslaitoksen kartta" + url=" https://tiles.kartat.kapsi.fi/taustakartta/{z}/{x}/{y}.jpg" /> - <br /> - <label>Map things</label> - <br /> - <Map - id="newGameCenterMap" - className="" - center={[this.state.mapCenter.lat, this.state.mapCenter.lng]} - zoom={this.state.zoom} - style={{ height: "400px", width: "400px" }} - onmoveend={this.handleMapDrag} - onzoomend={this.handleMapScroll} - > - <TileLayer - attribution="Maanmittauslaitoksen kartta" - url=" https://tiles.kartat.kapsi.fi/taustakartta/{z}/{x}/{y}.jpg" - /> - </Map> - <br /> - <button id="newGameSubmitButton" type="submit"> - Submit - </button> - <h2>{this.state.errorMsg}</h2> - </form> + </Map> + <br /> + <button + id="newGameSubmitButton" + type="submit" + form="gameCreationForm" + > + Submit + </button> + <h2>{this.state.errorMsg}</h2> </div> </div>, document.getElementById("form") diff --git a/src/components/RegisterForm.js b/src/components/RegisterForm.js index bba11b805f7a58f154097370ea3fbcaac4bd5cb9..8c40fd59c58554dfa5604633fe670bd580c51053 100644 --- a/src/components/RegisterForm.js +++ b/src/components/RegisterForm.js @@ -62,7 +62,7 @@ export class RegisterForm extends React.Component { this.props.handleState(result); this.handleView(); } else { - this.handleError(result.errorResponse.message); + this.handleError(result.message); } }, // Note: it's important to handle errors here @@ -82,7 +82,7 @@ export class RegisterForm extends React.Component { componentWillUnmount() { document.removeEventListener("keyup", this.handleEsc); } - + // UNCOMMENT "REQUIRED" FOR PRODUCTION render() { return ( <div className="fade-main"> diff --git a/src/components/TaskItem.js b/src/components/TaskItem.js new file mode 100644 index 0000000000000000000000000000000000000000..620ba85b27a310ef6a284cfd563e88695d5cfa0a --- /dev/null +++ b/src/components/TaskItem.js @@ -0,0 +1,119 @@ +import React from "react"; + +class TaskItem extends React.Component { + constructor(props) { + super(props); + this.state = { + edit: false, + selectedFactionId: "", + factions: [] + }; + } + + componentDidMount() { + this.getFactionlist(this.props.gameId); + } + + onEditClick = e => { + this.setState({ + edit: !this.state.edit + }); + }; + + getFactionlist(gameId) { + fetch(`${process.env.REACT_APP_URL}/game/${gameId}`, { + method: "GET" + }) + .then(result => { + if (!result.ok) { + throw Error(result.responseText); + } else { + return result.json(); + } + }) + .then(result => { + this.setState({ + factions: result.factions, + selectedFactionId: result.factions[0].factionId + }); + }) + .catch(error => console.log(error)); + } + + onSaveSubmit = e => { + e.preventDefault(); + this.props.onSave(this.props.task, this.state.selectedFactionId); + this.setState({ + edit: false + }); + }; + + handleFactionChange = e => { + this.setState({ + selectedFactionId: e.target.value + }); + }; + + onTaskDelete = e => { + e.preventDefault(); + this.props.onDelete(this.props.task.taskId); + this.setState({ + edit: false + }); + }; + + render() { + let factionlistItems = []; + for (let i = 0; i < this.state.factions.length; i++) { + const faction = this.state.factions[i]; + factionlistItems.push( + <option key={faction.factionId} value={faction.factionId}> + {faction.factionName} + </option> + ); + } + + return ( + <div className="tasklist-item"> + <div> + <label>{this.props.task.taskName}</label> + </div> + <div> + <label>{this.props.task.taskDescription}</label> + <br /> + <label> + Faction:{" "} + {this.props.task.faction !== null + ? this.props.task.faction.factionName + : "Every faction"} + </label> + <br /> + {this.props.task.taskWinner !== null && ( + <label>Winner: {this.props.task.taskWinner.factionName}</label> + )} + </div> + {this.props.task.taskIsActive && ( + <button onClick={this.onEditClick}>Edit</button> + )} + {this.state.edit && ( + <form onSubmit={this.onSaveSubmit}> + <select + value={this.state.selectedFactionId} + onChange={e => + this.setState({ selectedFactionId: e.target.value }) + } + > + {factionlistItems} + </select> + <button type="submit">Save</button> + </form> + )} + <button onClick={this.onTaskDelete} style={{ backgroundColor: "red" }}> + Delete + </button> + </div> + ); + } +} + +export default TaskItem; diff --git a/src/components/TaskList.js b/src/components/TaskList.js new file mode 100644 index 0000000000000000000000000000000000000000..fedbc03ae0dbac449f8aa4c7518dcca03597b897 --- /dev/null +++ b/src/components/TaskList.js @@ -0,0 +1,255 @@ +import ReactDOM from "react-dom"; +import React from "react"; +import TaskItem from "./TaskItem"; + +class TaskList extends React.Component { + constructor(props) { + super(props); + this.state = { + taskNameInput: "", // >= 3 + taskDescriptionInput: "", // no limits + tasks: [], + factionlist: [], + selectedFactionId: "" + }; + } + + componentDidMount() { + this.getTasks(this.props.gameId); + this.getFactionlist(this.props.gameId); // TODO: remove if the user is not admin? + } + + getTasks(gameId) { + let token = sessionStorage.getItem("token"); + fetch(`${process.env.REACT_APP_URL}/task/get-tasks/${gameId}`, { + method: "GET", + headers: { + Authorization: "Bearer " + token + } + }) + .then(result => { + if (!result.ok) { + throw Error(result.responseText); + } else { + return result.json(); + } + }) + .then(result => { + this.setState({ + tasks: result + }); + }) + .catch(error => console.log(error)); + } + + getFactionlist(gameId) { + fetch(`${process.env.REACT_APP_URL}/game/${gameId}`, { + method: "GET" + }) + .then(result => { + if (!result.ok) { + throw Error(result.responseText); + } else { + return result.json(); + } + }) + .then(result => { + this.setState({ + factionlist: result.factions + }); + }) + .catch(error => console.log(error)); + } + + handleTaskCreation = e => { + e.preventDefault(); + if (this.state.taskNameInput === "") { + return alert("Task needs a name"); + } + + 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 + }) + }) + .then(result => { + if (!result.ok) { + throw Error(Response.statusText); + } else { + return result.json(); + } + }) + .then(result => { + alert(result.message); + this.setState({ + taskDescriptionInput: "", + taskNameInput: "" + }); + this.getTasks(this.props.gameId); + }) + .catch(error => console.log(error)); + }; + + handleFactionChange = e => { + this.setState({ + selectedFactionId: e.target.value + }); + }; + + 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 + }) + }) + .then(result => { + if (!result.ok) { + throw Error(result.responseText); + } else { + return result.json(); + } + }) + .then(result => { + alert(result.message); + this.getTasks(this.props.gameId); + }) + .catch(error => console.log(error)); + }; + + onTaskDeletion = taskId => { + if (taskId === (undefined || null)) { + return; + } + let token = sessionStorage.getItem("token"); + fetch( + `${process.env.REACT_APP_URL}/task/delete-task/${this.props.gameId}`, + { + method: "DELETE", + headers: { + Authorization: "Bearer " + token, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + taskId: taskId + }) + } + ) + .then(result => { + if (!result.ok) { + throw Error(result.responseText); + } else { + return result.json(); + } + }) + .then(result => { + alert(result.message); + this.getTasks(this.props.gameId); + }) + .catch(error => console.log(error)); + }; + + render() { + let incompleteTasks = []; + let completedTasks = []; + for (let i = 0; i < this.state.tasks.length; i++) { + const task = this.state.tasks[i]; + if (task.taskWinner !== null) { + completedTasks.push( + <TaskItem + key={task.taskId} + task={task} + gameId={this.props.gameId} + onSave={this.onTaskEditSave} + onDelete={this.onTaskDeletion} + /> + ); + } else { + incompleteTasks.push( + <TaskItem + key={task.taskId} + task={task} + gameId={this.props.gameId} + onSave={this.onTaskEditSave} + onDelete={this.onTaskDeletion} + /> + ); + } + } + + let factionlistItems = this.state.factionlist.map(item => { + return ( + <option key={item.factionId} value={item.factionId}> + {item.factionName} + </option> + ); + }); + + // add all factions option to the faction list + factionlistItems.unshift( + <option key="all" value=""> + Every faction + </option> + ); + + 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> + {incompleteTasks} + <br /> + <label>Completed tasks</label> + {completedTasks} + <br /> + </div>, + document.getElementById("tasklist") + ); + } +} + +export default TaskList; diff --git a/src/components/TaskListButton.js b/src/components/TaskListButton.js new file mode 100644 index 0000000000000000000000000000000000000000..988beab9c3b21520190f5fde33774e76ac74ba35 --- /dev/null +++ b/src/components/TaskListButton.js @@ -0,0 +1,53 @@ +import React, { Fragment } from "react"; +import TaskList from "./TaskList"; + +class TaskListButton extends React.Component { + constructor(props) { + super(props); + this.state = { + open: false, + newTasks: 0 + }; + + this.handleClick = this.handleClick.bind(this); + } + + componentDidMount() { + this.getNewTask(); + } + + getNewTask() { + this.setState({ + newTasks: this.state.open ? 0 : this.state.newTasks + 1 + }); + } + + handleClick = e => { + this.setState( + { + open: !this.state.open + }, + () => { + // Set new task cout to zero when the tasklist opens + if (this.state.open) { + this.setState({ newTasks: 0 }); + } + } + ); + }; + + render() { + return ( + <Fragment> + <button id="tasklistButton" onClick={this.handleClick}> + Tasks ({this.state.newTasks}) + </button> + {this.state.open && ( + <TaskList gameId="2c097e6a-591c-4a27-b7cb-38eb44e1f31c" /> + )} + </Fragment> + ); + } +} + +export default TaskListButton;