From 04e779738e1d4aedf1b1df9a1f49759b779bedd5 Mon Sep 17 00:00:00 2001 From: Jonny Date: Tue, 28 May 2019 15:30:56 +0200 Subject: [PATCH 1/6] Add codacy badge to README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1ac9816..83eb16d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # turniere-frontend +[![Codacy Badge](https://api.codacy.com/project/badge/Grade/300915a8466f4f059150b543e9a6d1b0)](https://app.codacy.com/app/JP1998/turniere-frontend?utm_source=github.com&utm_medium=referral&utm_content=turniere/turniere-frontend&utm_campaign=Badge_Grade_Dashboard) + ## Development Setup ### Prerequisites From 3bb902b03316e422573e1b574c5d4a676f3a4086 Mon Sep 17 00:00:00 2001 From: JP1998 Date: Tue, 28 May 2019 16:54:51 +0200 Subject: [PATCH 2/6] Check for invalid inputs before applying new group size --- pages/create.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pages/create.js b/pages/create.js index 5cfa2ec..2fdc781 100644 --- a/pages/create.js +++ b/pages/create.js @@ -163,12 +163,14 @@ class CreateTournamentForm extends React.Component { handleGroupSizeInput(input) { const newSize = input.target.value; - if (newSize <= this.state.groupAdvance) { - this.setState({ - groupSize: newSize, groupAdvance: newSize - 1 - }); - } else { - this.setState({groupSize: newSize}); + if (newSize !== undefined && newSize > 2) { + if (newSize <= this.state.groupAdvance) { + this.setState({ + groupSize: newSize, groupAdvance: newSize - 1 + }); + } else { + this.setState({groupSize: newSize}); + } } } From 7848d3d81d63b96239aadcff597c63a468b27564 Mon Sep 17 00:00:00 2001 From: Felix Hamme Date: Tue, 28 May 2019 21:07:36 +0200 Subject: [PATCH 3/6] Implement starting a match, make backend calls --- js/api.js | 25 +++++++++++++++++++++++++ js/components/Match.js | 29 +++++++++++++++++++++++++---- js/redux/tournamentInfo.js | 2 ++ 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/js/api.js b/js/api.js index 3510c72..8c83999 100644 --- a/js/api.js +++ b/js/api.js @@ -221,6 +221,19 @@ const reducerTournamentinfo = (state = defaultStateTournamentinfo, action) => { return Object.assign({}, state, {}); case actionTypesTournamentinfo.REHYDRATE: + return Object.assign({}, state, {}); + case actionTypesTournamentinfo.START_MATCH: + patchRequest(action.state, '/matches/' + action.parameters.matchId, { + state: 'in_progress' + }).then(resp => { + storeOptionalToken(resp); + action.parameters.successCallback(); + }).catch(error => { + if (error.response) { + storeOptionalToken(error.response); + } + action.parameters.errorCallback(); + }); return Object.assign({}, state, {}); case actionTypesTournamentinfo.CLEAR: @@ -364,6 +377,18 @@ export function updateTeamName(team, successCB, errorCB) { }); } +export function startMatch(matchId, successCallback, errorCallback) { + __store.dispatch({ + type: actionTypesTournamentinfo.START_MATCH, + parameters: { + matchId: matchId, + successCallback: successCallback, + errorCallback: errorCallback + }, + state: __store.getState() + }); +} + export function getState() { return __store.getState(); } diff --git a/js/components/Match.js b/js/components/Match.js index eb99e63..4b083fa 100644 --- a/js/components/Match.js +++ b/js/components/Match.js @@ -12,15 +12,21 @@ import { Table } from 'reactstrap'; import React from 'react'; +import {startMatch} from '../api'; +import {notify} from 'react-notify-toast'; export class Match extends React.Component { constructor(props) { super(props); this.state = { - modal: false + modal: false, + matchState: this.props.match.state }; this.toggleModal = this.toggleModal.bind(this); + this.startMatch = this.startMatch.bind(this); + this.onStartMatchSuccess = this.onStartMatchSuccess.bind(this); + this.onStartMatchError = this.onStartMatchError.bind(this); } toggleModal() { @@ -31,12 +37,26 @@ export class Match extends React.Component { } } + startMatch() { + startMatch(this.props.match.id, this.onStartMatchSuccess, this.onStartMatchError); + } + + onStartMatchSuccess() { + this.setState({matchState: 'in_progress'}); + this.toggleModal(); + } + + onStartMatchError() { + this.toggleModal(); + notify.show('Das Match konnte nicht gestartet werden.', 'error', 3000); + } + render() { let cardClass; let smallMessage; let borderClass; // possible states: single_team not_ready not_started in_progress team1_won team2_won undecided - switch (this.props.match.state) { + switch (this.state.matchState) { case 'in_progress': cardClass = 'table-warning'; borderClass = 'border-warning'; @@ -76,7 +96,8 @@ export class Match extends React.Component { {smallMessage} - + ); } } @@ -104,7 +125,7 @@ function MatchModal(props) { break; case 'not_started': title = 'Spiel kann gestartet werden'; - actionButton = ; + actionButton = ; break; case 'undecided': title = 'Spiel beendet'; diff --git a/js/redux/tournamentInfo.js b/js/redux/tournamentInfo.js index 3322f04..8a1f1df 100644 --- a/js/redux/tournamentInfo.js +++ b/js/redux/tournamentInfo.js @@ -8,6 +8,8 @@ export const actionTypesTournamentinfo = { 'MODIFY_TOURNAMENT_SUCCESS': 'MODIFY_TOURNAMENT_SUCCESS', 'MODIFY_TOURNAMENT_ERROR': 'MODIFY_TOURNAMENT_ERROR', + 'START_MATCH': 'START_MATCH', + 'REHYDRATE': 'TOURNAMENTINFO_REHYDRATE', 'CLEAR': 'TOURNAMENTINFO_CLEAR' }; From 2defc3df22dce66eb94274949ed2861156938267 Mon Sep 17 00:00:00 2001 From: JP1998 Date: Tue, 28 May 2019 22:55:19 +0200 Subject: [PATCH 4/6] Refactor the handleGroupSizeInput method --- pages/create.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/pages/create.js b/pages/create.js index 2fdc781..b49123f 100644 --- a/pages/create.js +++ b/pages/create.js @@ -163,14 +163,17 @@ class CreateTournamentForm extends React.Component { handleGroupSizeInput(input) { const newSize = input.target.value; - if (newSize !== undefined && newSize > 2) { - if (newSize <= this.state.groupAdvance) { - this.setState({ - groupSize: newSize, groupAdvance: newSize - 1 - }); - } else { - this.setState({groupSize: newSize}); - } + + if (newSize === undefined || newSize < 2) { + return; + } + + if (newSize <= this.state.groupAdvance) { + this.setState({ + groupSize: newSize, groupAdvance: newSize - 1 + }); + } else { + this.setState({groupSize: newSize}); } } From 3fc8a5d2917fc30a223dd6472c5ceb96496f2935 Mon Sep 17 00:00:00 2001 From: JP1998 Date: Tue, 28 May 2019 22:57:41 +0200 Subject: [PATCH 5/6] Check the group advance input for valid values --- pages/create.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pages/create.js b/pages/create.js index b49123f..97a3755 100644 --- a/pages/create.js +++ b/pages/create.js @@ -163,7 +163,7 @@ class CreateTournamentForm extends React.Component { handleGroupSizeInput(input) { const newSize = input.target.value; - + if (newSize === undefined || newSize < 2) { return; } @@ -178,7 +178,14 @@ class CreateTournamentForm extends React.Component { } handleGroupAdvanceInput(input) { - this.setState({groupAdvance: input.target.value}); + const newAdvance = input.target.value; + + if (newAdvance === undefined || newAdvance <= 0 || + newAdvance >= this.state.groupSize) { + return; + } + + this.setState({groupAdvance: newAdvance}); } handleGroupPhaseEnabledInput(input) { From 134844973e478a1e5cecc97d1cc7e26c24d8bea2 Mon Sep 17 00:00:00 2001 From: Felix Hamme Date: Wed, 29 May 2019 00:00:54 +0200 Subject: [PATCH 6/6] Implement ending a match: make backend calls, replace match states {team1_won, team2_won, undecided} with {finished}, hold the match data in Match.js in state instead of in props, reorder internal match data storage --- js/api.js | 25 ++++++++ js/components/Match.js | 128 ++++++++++++++++++++++--------------- js/redux/tournamentInfo.js | 1 + pages/tournament.js | 44 +++++++++---- 4 files changed, 133 insertions(+), 65 deletions(-) diff --git a/js/api.js b/js/api.js index 8c83999..d90ea5d 100644 --- a/js/api.js +++ b/js/api.js @@ -235,6 +235,19 @@ const reducerTournamentinfo = (state = defaultStateTournamentinfo, action) => { action.parameters.errorCallback(); }); return Object.assign({}, state, {}); + case actionTypesTournamentinfo.END_MATCH: + patchRequest(action.state, '/matches/' + action.parameters.matchId, { + state: 'finished' + }).then(resp => { + storeOptionalToken(resp); + action.parameters.successCallback(resp.data.winner); + }).catch(error => { + if (error.response) { + storeOptionalToken(error.response); + } + action.parameters.errorCallback(); + }); + return Object.assign({}, state, {}); case actionTypesTournamentinfo.CLEAR: return Object.assign({}, state, {}); @@ -389,6 +402,18 @@ export function startMatch(matchId, successCallback, errorCallback) { }); } +export function endMatch(matchId, successCallback, errorCallback) { + __store.dispatch({ + type: actionTypesTournamentinfo.END_MATCH, + parameters: { + matchId: matchId, + successCallback: successCallback, + errorCallback: errorCallback + }, + state: __store.getState() + }); +} + export function getState() { return __store.getState(); } diff --git a/js/components/Match.js b/js/components/Match.js index 4b083fa..ebf91a7 100644 --- a/js/components/Match.js +++ b/js/components/Match.js @@ -12,7 +12,7 @@ import { Table } from 'reactstrap'; import React from 'react'; -import {startMatch} from '../api'; +import {endMatch, startMatch} from '../api'; import {notify} from 'react-notify-toast'; @@ -21,12 +21,16 @@ export class Match extends React.Component { super(props); this.state = { modal: false, - matchState: this.props.match.state + match: this.props.match }; this.toggleModal = this.toggleModal.bind(this); this.startMatch = this.startMatch.bind(this); this.onStartMatchSuccess = this.onStartMatchSuccess.bind(this); this.onStartMatchError = this.onStartMatchError.bind(this); + this.endMatch = this.endMatch.bind(this); + this.onEndMatchSuccess = this.onEndMatchSuccess.bind(this); + this.onEndMatchError = this.onEndMatchError.bind(this); + this.getMatchFinishedMessage = this.getMatchFinishedMessage.bind(this); } toggleModal() { @@ -38,11 +42,13 @@ export class Match extends React.Component { } startMatch() { - startMatch(this.props.match.id, this.onStartMatchSuccess, this.onStartMatchError); + startMatch(this.state.match.id, this.onStartMatchSuccess, this.onStartMatchError); } onStartMatchSuccess() { - this.setState({matchState: 'in_progress'}); + const updatedMatch = this.state.match; + updatedMatch.state = 'in_progress'; + this.setState({match: updatedMatch}); this.toggleModal(); } @@ -51,26 +57,52 @@ export class Match extends React.Component { notify.show('Das Match konnte nicht gestartet werden.', 'error', 3000); } + endMatch() { + endMatch(this.state.match.id, this.onEndMatchSuccess, this.onEndMatchError); + } + + onEndMatchSuccess(winner) { + const updatedMatch = this.state.match; + updatedMatch.state = 'finished'; + updatedMatch.winnerTeamId = winner === null ? null : winner.id; + this.setState({match: updatedMatch}); + this.toggleModal(); + } + + onEndMatchError() { + this.toggleModal(); + notify.show('Das Match konnte nicht beendet werden.', 'error', 3000); + } + + getMatchFinishedMessage() { + const match = this.state.match; + if (match.winnerTeamId === null) { + return 'Spiel beendet, unentschieden'; + } + if (match.winnerTeamId === match.team1.id) { + return 'Gewinner: ' + match.team1.name; + } + if (match.winnerTeamId === match.team2.id) { + return 'Gewinner: ' + match.team2.name; + } + return 'Spiel beendet'; + } + render() { let cardClass; let smallMessage; let borderClass; - // possible states: single_team not_ready not_started in_progress team1_won team2_won undecided - switch (this.state.matchState) { + // possible states: single_team not_ready not_started in_progress finished + switch (this.state.match.state) { case 'in_progress': cardClass = 'table-warning'; borderClass = 'border-warning'; smallMessage = 'Spiel läuft'; break; - case 'team1_won': + case 'finished': cardClass = 'table-success'; borderClass = 'border-success'; - smallMessage = 'Gewinner: ' + this.props.match.team1; - break; - case 'team2_won': - cardClass = 'table-success'; - borderClass = 'border-success'; - smallMessage = 'Gewinner: ' + this.props.match.team2; + smallMessage = this.getMatchFinishedMessage(); break; case 'single_team': cardClass = 'table-success'; @@ -83,21 +115,16 @@ export class Match extends React.Component { case 'not_started': smallMessage = 'Spiel kann gestartet werden'; break; - case 'undecided': - cardClass = 'table-success'; - borderClass = 'border-success'; - smallMessage = 'Spiel beendet, unentschieden'; - break; } return (
- + {smallMessage} - +
); } } @@ -105,16 +132,13 @@ export class Match extends React.Component { function MatchModal(props) { let title; let actionButton = ''; - // possible states: single_team not_ready not_started in_progress team1_won team2_won undecided + // possible states: single_team not_ready not_started in_progress finished switch (props.match.state) { case 'in_progress': title = 'Spiel läuft'; - actionButton = ; + actionButton = ; break; - case 'team1_won': - title = 'Spiel beendet'; - break; - case 'team2_won': + case 'finished': title = 'Spiel beendet'; break; case 'single_team': @@ -127,15 +151,12 @@ function MatchModal(props) { title = 'Spiel kann gestartet werden'; actionButton = ; break; - case 'undecided': - title = 'Spiel beendet'; - break; } return ( {title} - {props.match.state === 'in_progress' ? : - } + {props.matchState === 'in_progress' ? : + } {actionButton} @@ -147,17 +168,22 @@ function MatchModal(props) { function MatchTable(props) { let team1Class; let team2Class; - // possible states: single_team not_ready not_started in_progress team1_won team2_won undecided - switch (props.match.state) { + // possible states: single_team not_ready not_started in_progress finished + switch (props.matchState) { case 'in_progress': break; - case 'team1_won': - team1Class = 'font-weight-bold'; - team2Class = 'lost-team'; - break; - case 'team2_won': - team1Class = 'lost-team'; - team2Class = 'font-weight-bold'; + case 'finished': + if (props.match.winnerTeamId === undefined) { + break; + } + if (props.winnerTeamId === props.match.team1.id) { + team1Class = 'font-weight-bold'; + team2Class = 'lost-team'; + } + if (props.winnerTeamId === props.match.team2.id) { + team1Class = 'lost-team'; + team2Class = 'font-weight-bold'; + } break; case 'single_team': team2Class = 'text-muted'; @@ -166,14 +192,12 @@ function MatchTable(props) { break; case 'not_started': break; - case 'undecided': - break; } if (props.match.state === 'single_team') { return ( - + @@ -184,12 +208,12 @@ function MatchTable(props) { return (
{props.match.team1}{props.match.team1.name}
kein Gegner
- - + + - - + +
{props.match.scoreTeam1}{props.match.team1}{props.match.team1.score}{props.match.team1.name}
{props.match.scoreTeam2}{props.match.team2}{props.match.team2.score}{props.match.team2.name}
); @@ -201,15 +225,15 @@ function EditableMatchTable(props) { - + - {props.match.team1} + {props.match.team1.name} - + - {props.match.team2} + {props.match.team2.name} ); diff --git a/js/redux/tournamentInfo.js b/js/redux/tournamentInfo.js index 8a1f1df..f5aedbe 100644 --- a/js/redux/tournamentInfo.js +++ b/js/redux/tournamentInfo.js @@ -9,6 +9,7 @@ export const actionTypesTournamentinfo = { 'MODIFY_TOURNAMENT_ERROR': 'MODIFY_TOURNAMENT_ERROR', 'START_MATCH': 'START_MATCH', + 'END_MATCH': 'END_MATCH', 'REHYDRATE': 'TOURNAMENTINFO_REHYDRATE', 'CLEAR': 'TOURNAMENTINFO_CLEAR' diff --git a/pages/tournament.js b/pages/tournament.js index 8cf898c..a7bc42e 100644 --- a/pages/tournament.js +++ b/pages/tournament.js @@ -121,24 +121,42 @@ function convertGroup(apiGroup) { function convertMatch(apiMatch) { const result = { - id: apiMatch.id, state: apiMatch.state + id: apiMatch.id, state: apiMatch.state, winnerTeamId: apiMatch.winner === null ? null : apiMatch.winner.id }; if (apiMatch.match_scores.length === 2) { - result.team1 = apiMatch.match_scores[0].team.name; - result.scoreTeam1 = apiMatch.match_scores[0].points; - result.team2 = apiMatch.match_scores[1].team.name; - result.scoreTeam2 = apiMatch.match_scores[1].points; + result.team1 = { + name: apiMatch.match_scores[0].team.name, + id: apiMatch.match_scores[0].team.id, + score: apiMatch.match_scores[0].points + }; + result.team2 = { + name: apiMatch.match_scores[1].team.name, + id: apiMatch.match_scores[1].team.id, + score: apiMatch.match_scores[1].points + }; } else if (apiMatch.match_scores.length === 1) { - result.team1 = apiMatch.match_scores[0].team.name; - result.scoreTeam1 = apiMatch.match_scores[0].points; - result.team2 = 'TBD'; - result.scoreTeam2 = 0; + result.team1 = { + name: apiMatch.match_scores[0].team.name, + id: apiMatch.match_scores[0].team.id, + score: apiMatch.match_scores[0].points + }; + result.team2 = { + name: 'TBD', + id: null, + score: 0 + }; } else { - result.team1 = 'TBD'; - result.scoreTeam1 = 0; - result.team2 = 'TBD'; - result.scoreTeam2 = 0; + result.team1 = { + name: 'TBD', + id: null, + score: 0 + }; + result.team2 = { + name: 'TBD', + id: null, + score: 0 + }; } return result;