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..61ee8dc196700804d4f532ea9d6751894a57cd97 100644 --- a/src/App.css +++ b/src/App.css @@ -197,3 +197,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/Header.js b/src/components/Header.js index 0663cc382019e1aa00f9757a98b4fe5fb1a5508f..422e9be78a823b7c2f778898b0c00a64381cb04a 100644 --- a/src/components/Header.js +++ b/src/components/Header.js @@ -1,9 +1,10 @@ -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 GameList from "./GameList"; +import NewGameForm from "./NewGameForm"; +import TaskListButton from "./TaskListButton"; class Header extends React.Component { constructor(props) { @@ -11,7 +12,7 @@ class Header extends React.Component { } state = { - form: '', // Popup form (login, register etc.) + form: "", // Popup form (login, register etc.) username: null, token: null }; @@ -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,57 @@ 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')} + 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 />} </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='' + view="" handleState={this.handleState} toggleView={this.toggleView} /> )} - {this.state.form === 'newgame' && ( + {this.state.form === "newgame" && ( <NewGameForm - view='' + view="" handleState={this.handleState} toggleView={this.toggleView} /> diff --git a/src/components/TaskItem.js b/src/components/TaskItem.js new file mode 100644 index 0000000000000000000000000000000000000000..7b53d079ca501eef11a9a1bcc57e5f096cf10a36 --- /dev/null +++ b/src/components/TaskItem.js @@ -0,0 +1,119 @@ +import React, { Fragment } 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;