From 0b35c6c84da0b55b23a60046a9a8fefc1a864037 Mon Sep 17 00:00:00 2001 From: Felix Hamme Date: Wed, 17 Apr 2019 09:59:09 +0200 Subject: [PATCH 01/19] Begin implementing page for private tournaments --- js/api.js | 84 ++++++++++++++++++++++++++++-- pages/private.js | 130 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 211 insertions(+), 3 deletions(-) create mode 100644 pages/private.js diff --git a/js/api.js b/js/api.js index a065064..0db5c10 100644 --- a/js/api.js +++ b/js/api.js @@ -32,7 +32,7 @@ const actiontypes_userinfo = { 'STORE_AUTH_HEADERS' : 'STORE_AUTH_HEADERS', 'REHYDRATE' : 'USERINFO_REHYDRATE', - 'CLEAR' : 'USERINFO_CLEAR', + 'CLEAR' : 'USERINFO_CLEAR' }; const defaultstate_userinfo = { @@ -72,6 +72,17 @@ const defaultstate_tournamentinfo = { teams : [] }; +const actiontypes_tournamentlist = { + 'FETCH': 'FETCH', + 'FETCH_SUCCESS': 'FETCH_SUCCESS', + 'FETCH_ERROR': 'FETCH_ERROR', + 'REHYDRATE': 'REHYDRATE' +}; + +const defaultstate_tournamentlist = { + tournaments: [] +}; + export function postRequest(state, url, data) { return axios.post(api_url + url, data, { headers : generateHeaders(state) @@ -263,6 +274,27 @@ const reducer_userinfo = (state = defaultstate_userinfo, action) => { expiry : null, uid : null }); + case actiontypes_userinfo.GET_TOURNAMENT_LIST: + getRequest(action.state, '/tournaments?type=' + action.parameters.type).then((resp) => { + __store.dispatch({ + type: actiontypes_tournamentinfo.GET_TOURNAMENT_LIST_SUCCESS, + parameters: resp + }); + storeOptionalToken(resp); + action.parameters.successCallback(); + }).catch((error) => { + __store.dispatch({ + type: actiontypes_tournamentinfo.GET_TOURNAMENT_LIST_ERROR, + parameters: {error: error} + }); + storeOptionalToken(error.response); + action.parameters.errorCallback(); + }); + return Object.assign({}, state, {}); + case actiontypes_userinfo.GET_TOURNAMENT_LIST_ERROR: + return Object.assign({}, state, {}); + case actiontypes_userinfo.GET_TOURNAMENT_LIST_SUCCESS: + return Object.assign({}, state, {...action.parameters}); default: return state; } }; @@ -329,14 +361,44 @@ const reducer_tournamentinfo = (state = defaultstate_tournamentinfo, action) => } }; +const reducer_tournamentlist = (state = defaultstate_tournamentlist, action) => { + switch (action.type) { + case actiontypes_tournamentlist.FETCH: + getRequest(action.state, '/tournaments?type=' + action.parameters.type).then((resp) => { + __store.dispatch({ + type: actiontypes_tournamentlist.FETCH_SUCCESS, + parameters: resp + }); + storeOptionalToken(resp); + action.parameters.successCallback(); + }).catch((error) => { + __store.dispatch({ + type: actiontypes_tournamentlist.FETCH_ERROR, + parameters: {error: error} + }); + storeOptionalToken(error.response); + action.parameters.errorCallback(); + }); + return state; + case actiontypes_tournamentlist.FETCH_SUCCESS: + return Object.assign({}, state, {...action.parameters}); + case actiontypes_tournamentlist.FETCH_ERROR: + return state; + default: + return state; + } +}; + const reducers = { userinfo: reducer_userinfo, - tournamentinfo: reducer_tournamentinfo + tournamentinfo: reducer_tournamentinfo, + tournamentlist: reducer_tournamentlist }; const default_applicationstate = { userinfo : defaultstate_userinfo, - tournamentinfo: defaultstate_tournamentinfo + tournamentinfo: defaultstate_tournamentinfo, + tournamentlist: defaultstate_tournamentlist }; var __store; @@ -444,6 +506,18 @@ export function getState() { return __store.getState(); } +export function requestTournamentList(myType, successCallback, errorCallback) { + __store.dispatch({ + type: actiontypes_tournamentlist.FETCH, + parameters: { + type: myType, + successCallback: successCallback, + errorCallback: errorCallback + }, + state: __store.getState() + }); +} + function rehydrateApplicationState() { const persistedState = localStorage.getItem('reduxState') ? JSON.parse(localStorage.getItem('reduxState')) : @@ -458,6 +532,10 @@ function rehydrateApplicationState() { type : actiontypes_tournamentinfo.REHYDRATE, parameters : Object.assign({}, persistedState.tournamentinfo) }); + __store.dispatch({ + type : actiontypes_tournamentlist.REHYDRATE, + parameters : Object.assign({}, persistedState.tournamentlist) + }); applicationHydrated = true; } } diff --git a/pages/private.js b/pages/private.js new file mode 100644 index 0000000..8d732b1 --- /dev/null +++ b/pages/private.js @@ -0,0 +1,130 @@ +import Head from 'next/head'; +import React from 'react'; +import {connect} from 'react-redux'; + +import {Card, CardBody, Container,} from 'reactstrap'; + +import {TurniereNavigation} from '../js/components/Navigation'; +import {Footer} from '../js/components/Footer'; +import {Option, UserRestrictor} from '../js/components/UserRestrictor'; +import {Login} from '../js/components/Login'; +import {requestTournamentList} from '../js/api'; + +import '../static/everypage.css'; + +class PrivateTournamentsPage extends React.Component { + + render() { + const {isSignedIn} = this.props; + + return ( + + + + + ); + } +} + +function mapStateToCreatePageProperties(state) { + const {isSignedIn} = state.userinfo; + return {isSignedIn}; +} + +const CreatePage = connect( + mapStateToCreatePageProperties, +)(PrivateTournamentsPage); + +export default CreatePage; + + + +class PrivateTournamentsListCard extends React.Component { + constructor(props) { + super(props); + this.state = { + error: null, + isLoaded: false, + items: [] + }; + } + + componentDidMount() { + requestTournamentList('private', response => { + this.setState({ + isLoaded: true, + items: response.data + }); + }, + error => { + this.setState({ + isLoaded: true, + error + }); + }); + /* + getRequest(getState(), '/tournaments?type=private') + .then( + response => { + this.setState({ + isLoaded: true, + items: response.data + }); + }, + error => { + this.setState({ + isLoaded: true, + error + }); + } + ); + */ + } + + render() { + return ( + + + +

Private Turniere

+ {this.state.items.map(item => ( + //The code should be item.code but the api just supports it this way by now + + ))} +
+
+
+ ); + } +} + +function TournamentListEntry(props) { + return ( + + {props.name} + + ); +} \ No newline at end of file From 0aa1660cd1c6c2cecb1c7080c1e863805f2021e4 Mon Sep 17 00:00:00 2001 From: Felix Hamme Date: Wed, 17 Apr 2019 09:59:51 +0200 Subject: [PATCH 02/19] Link private tournament list in navigation bar if the user is signed in --- js/components/Navigation.js | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/js/components/Navigation.js b/js/components/Navigation.js index b9d0775..a58a33f 100644 --- a/js/components/Navigation.js +++ b/js/components/Navigation.js @@ -40,11 +40,7 @@ export class TurniereNavigation extends React.Component { - + @@ -60,6 +56,18 @@ function Navlink(props) { ); } +class SmartNavLinks extends React.Component { + + render() { + return (); + } +} + function Betabadge() { return (BETA); } @@ -87,12 +95,15 @@ class InvisibleLoginLogoutButtons extends React.Component { } } -const mapStateToLoginLogoutButtonProperties = (state) => { +const mapStateToUserinfo = (state) => { const { isSignedIn, username } = state.userinfo; return { isSignedIn, username }; }; const LoginLogoutButtons = connect( - mapStateToLoginLogoutButtonProperties + mapStateToUserinfo )(InvisibleLoginLogoutButtons); +const NavLinks = connect( + mapStateToUserinfo +)(SmartNavLinks); \ No newline at end of file From f97fc23a4c5f12ded85fd4601cd4e5d67cf9611f Mon Sep 17 00:00:00 2001 From: Jonny Date: Wed, 17 Apr 2019 16:30:00 +0200 Subject: [PATCH 03/19] Format the error messages for registering --- pages/register.js | 10 +++------- static/css/errormessages.css | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 7 deletions(-) create mode 100644 static/css/errormessages.css diff --git a/pages/register.js b/pages/register.js index 3395c3d..31fad19 100644 --- a/pages/register.js +++ b/pages/register.js @@ -21,9 +21,9 @@ import { } from '../js/api'; import '../static/everypage.css'; +import '../static/css/errormessages.css'; export default class RegisterPage extends React.Component { - render() { return (
@@ -42,7 +42,6 @@ export default class RegisterPage extends React.Component { } class Register extends React.Component { - componentDidMount() { clearErrors(); } @@ -69,11 +68,9 @@ class RegisterErrorList extends React.Component { const { error, errorMessages } = this.props; if(error) { return ( -
    +
      { errorMessages.map((message, index) => -
    • - {message} -
    • +
    • {message}
    • ) }
    ); @@ -93,7 +90,6 @@ const VisibleRegisterErrorList = connect( )(RegisterErrorList); class RegisterForm extends React.Component { - constructor(props) { super(props); diff --git a/static/css/errormessages.css b/static/css/errormessages.css new file mode 100644 index 0000000..d0babb6 --- /dev/null +++ b/static/css/errormessages.css @@ -0,0 +1,20 @@ + +.error-box { + border: 2px solid #dc3545; + border-radius: 4px; + + padding-right: 16px; + padding-left: 16px; + padding-top: 8px; + padding-bottom: 8px; +} + +.error-box > li { + color: #dc3545; + margin-right: 36px; + display: inline; +} + +.error-box > li:last-child { + margin-right: 0px; +} \ No newline at end of file From 0643b4bb0eb9dc824a5bb430a37539f32e1c3ac9 Mon Sep 17 00:00:00 2001 From: Jonny Date: Wed, 17 Apr 2019 16:32:49 +0200 Subject: [PATCH 04/19] Format the errors for logging in --- js/components/Login.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/components/Login.js b/js/components/Login.js index 6b8db0e..2d2b599 100644 --- a/js/components/Login.js +++ b/js/components/Login.js @@ -8,6 +8,8 @@ import { clearErrors } from '../api'; +import '../../static/css/errormessages.css'; + export function Login(props) { return ( @@ -31,7 +33,7 @@ class LoginErrorList extends React.Component { const { error, errorMessages } = this.props; if(error) { return ( -
      +
        { errorMessages.map((message, index) =>
      • {message} From dfb709d37e246633a882bcc481278bb75cfb31ef Mon Sep 17 00:00:00 2001 From: Jonny Date: Thu, 18 Apr 2019 10:19:37 +0200 Subject: [PATCH 05/19] Remove the wrapping data object from the response of login --- js/api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/api.js b/js/api.js index a065064..7877298 100644 --- a/js/api.js +++ b/js/api.js @@ -188,7 +188,7 @@ const reducer_userinfo = (state = defaultstate_userinfo, action) => { __store.dispatch({ type : actiontypes_userinfo.LOGIN_RESULT_SUCCESS, parameters : { - username : resp.data.data.username, + username : resp.data.username, } }); storeOptionalToken(resp); From 6dce02ae8365cbcb7c49abea7001629443bd0084 Mon Sep 17 00:00:00 2001 From: Felix Hamme Date: Mon, 22 Apr 2019 16:41:37 +0200 Subject: [PATCH 06/19] Tweak Design --- static/css/errormessages.css | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/static/css/errormessages.css b/static/css/errormessages.css index d0babb6..f7d6b93 100644 --- a/static/css/errormessages.css +++ b/static/css/errormessages.css @@ -2,19 +2,11 @@ .error-box { border: 2px solid #dc3545; border-radius: 4px; - - padding-right: 16px; - padding-left: 16px; - padding-top: 8px; - padding-bottom: 8px; + padding: 8px 16px; } .error-box > li { color: #dc3545; margin-right: 36px; - display: inline; -} - -.error-box > li:last-child { - margin-right: 0px; + list-style-type: none; } \ No newline at end of file From 45d2b221eb81e51e95df98e1e7a07cc35e1c9fbf Mon Sep 17 00:00:00 2001 From: Felix Hamme Date: Mon, 22 Apr 2019 16:43:14 +0200 Subject: [PATCH 07/19] Apply css files in the same order everywhere (eliminate warning by mini-css-extract-plugin) --- pages/register.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/register.js b/pages/register.js index 31fad19..6b05493 100644 --- a/pages/register.js +++ b/pages/register.js @@ -20,8 +20,8 @@ import { clearErrors } from '../js/api'; -import '../static/everypage.css'; import '../static/css/errormessages.css'; +import '../static/everypage.css'; export default class RegisterPage extends React.Component { render() { From 6af5f9dc72caec6d17d43adc71f8557669e93efe Mon Sep 17 00:00:00 2001 From: Jonny Date: Tue, 23 Apr 2019 11:27:02 +0200 Subject: [PATCH 08/19] Add logic for creation of the data for group stages --- pages/create.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/pages/create.js b/pages/create.js index 77ed3aa..799252e 100644 --- a/pages/create.js +++ b/pages/create.js @@ -217,7 +217,8 @@ class CreateTournamentForm extends React.Component { 'name': this.state.name, 'description': this.state.description, 'public': this.state.public, - 'teams': this.createTeamArray(this.state.teams) + 'group_stage': this.state.groupPhaseEnabled, + 'teams': this.createTeamArray(this.state.groupPhaseEnabled, this.state.groups, this.state.teams) }, () => { notify.show('Das Turnier wurde erfolgreich erstellt.', 'success', 5000); }, () => { @@ -225,11 +226,22 @@ class CreateTournamentForm extends React.Component { }); } - createTeamArray(teamnames) { + createTeamArray(/* boolean */ groupphase, /* String[][] */ groups, /* String[] */ teams) { let result = []; - for(let i = 0; i < teamnames.length; i++) { - result[i] = { 'name': teamnames[i] }; + if(groupphase) { + for(let groupNumber = 0; groupNumber < groups.length; groupNumber++) { + for(let groupMember = 0; groupMember < groups[groupNumber].length; groupMember++) { + result[result.length] = { + 'name': groups[groupNumber][groupMember], + 'group': groupNumber + }; + } + } + } else { + for(let i = 0; i < teams.length; i++) { + result[i] = { 'name': teams[i] }; + } } return result; From b85ce62b2f0e3f7687adbeaf57df33353ed77e16 Mon Sep 17 00:00:00 2001 From: Felix Hamme Date: Tue, 23 Apr 2019 15:41:43 +0200 Subject: [PATCH 09/19] Greet the user after login with a toast --- js/api.js | 17 ++++++++++++++++- js/components/Navigation.js | 14 ++++++++++---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/js/api.js b/js/api.js index 7877298..d178f70 100644 --- a/js/api.js +++ b/js/api.js @@ -20,7 +20,8 @@ const actiontypes_userinfo = { 'LOGIN' : 'LOGIN', 'LOGIN_RESULT_SUCCESS' : 'LOGIN_RESULT_SUCCESS', 'LOGIN_RESULT_ERROR' : 'LOGIN_RESULT_ERROR', - + 'GREET' : 'GREET', + 'CLEAR_ERRORS' : 'CLEAR_ERRORS', 'LOGOUT' : 'LOGOUT', @@ -37,6 +38,7 @@ const actiontypes_userinfo = { const defaultstate_userinfo = { isSignedIn : false, + wasGreeted : false, username : null, error : false, errorMessages : [], @@ -214,6 +216,7 @@ const reducer_userinfo = (state = defaultstate_userinfo, action) => { case actiontypes_userinfo.LOGIN_RESULT_SUCCESS: return Object.assign({}, state, { isSignedIn : true, + wasGreeted : false, error : false, errorMessages : [], username : action.parameters.username, @@ -235,6 +238,10 @@ const reducer_userinfo = (state = defaultstate_userinfo, action) => { __store.dispatch({ type : actiontypes_userinfo.CLEAR }); }); return Object.assign({}, state, {}); + case actiontypes_userinfo.GREET: + return Object.assign({}, state, { + wasGreeted : true, + }); case actiontypes_userinfo.STORE_AUTH_HEADERS: return Object.assign({}, state, { accesstoken : action.parameters.accesstoken, @@ -396,6 +403,14 @@ export function login(email, password) { }); } +export function greet() { + __store.dispatch({ + type: actiontypes_userinfo.GREET, + parameters: {}, + state: __store.getState() + }); +} + export function logout() { __store.dispatch({ type : actiontypes_userinfo.LOGOUT, diff --git a/js/components/Navigation.js b/js/components/Navigation.js index b9d0775..a1bbb77 100644 --- a/js/components/Navigation.js +++ b/js/components/Navigation.js @@ -13,7 +13,8 @@ import { import { connect } from 'react-redux'; import React from 'react'; -import { logout } from '../api'; +import {greet, logout} from '../api'; +import {notify} from "react-notify-toast"; export class TurniereNavigation extends React.Component { @@ -67,7 +68,12 @@ function Betabadge() { class InvisibleLoginLogoutButtons extends React.Component { render() { - const { isSignedIn, username } = this.props; + const {isSignedIn, username, wasGreeted} = this.props; + + if (isSignedIn && !wasGreeted) { + notify.show('Willkommen, ' + username + '!', 'success', 3000); + greet(); + } if(isSignedIn) { return ( @@ -88,8 +94,8 @@ class InvisibleLoginLogoutButtons extends React.Component { } const mapStateToLoginLogoutButtonProperties = (state) => { - const { isSignedIn, username } = state.userinfo; - return { isSignedIn, username }; + const {isSignedIn, username, wasGreeted} = state.userinfo; + return {isSignedIn, username, wasGreeted}; }; const LoginLogoutButtons = connect( From 7b38b503ef5030d18a8a5734ae1dae163b6d8f96 Mon Sep 17 00:00:00 2001 From: Felix Hamme Date: Tue, 23 Apr 2019 20:47:07 +0200 Subject: [PATCH 10/19] Say goodbye to the user when he logs out --- js/api.js | 3 ++- js/components/Navigation.js | 17 +++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/js/api.js b/js/api.js index d178f70..aae8724 100644 --- a/js/api.js +++ b/js/api.js @@ -38,7 +38,7 @@ const actiontypes_userinfo = { const defaultstate_userinfo = { isSignedIn : false, - wasGreeted : false, + wasGreeted : true, username : null, error : false, errorMessages : [], @@ -261,6 +261,7 @@ const reducer_userinfo = (state = defaultstate_userinfo, action) => { case actiontypes_userinfo.CLEAR: return Object.assign({}, state, { isSignedIn : false, + wasGreeted : false, username : null, error : false, errorMessages : [], diff --git a/js/components/Navigation.js b/js/components/Navigation.js index a1bbb77..cc4b07a 100644 --- a/js/components/Navigation.js +++ b/js/components/Navigation.js @@ -14,7 +14,7 @@ import { connect } from 'react-redux'; import React from 'react'; import {greet, logout} from '../api'; -import {notify} from "react-notify-toast"; +import {notify} from 'react-notify-toast'; export class TurniereNavigation extends React.Component { @@ -70,12 +70,12 @@ class InvisibleLoginLogoutButtons extends React.Component { render() { const {isSignedIn, username, wasGreeted} = this.props; - if (isSignedIn && !wasGreeted) { - notify.show('Willkommen, ' + username + '!', 'success', 3000); - greet(); - } - if(isSignedIn) { + if (!wasGreeted) { + notify.show('Willkommen, ' + username + '!', 'success', 2500); + greet(); + } + return ( @@ -83,6 +83,11 @@ class InvisibleLoginLogoutButtons extends React.Component { ); } else { + if (!wasGreeted) { + notify.show('Du bist jetzt abgemeldet!', 'success', 2500); + greet(); + } + return ( From 2217212a4aad29158a90b431e59a82535512dd5c Mon Sep 17 00:00:00 2001 From: JP1998 Date: Tue, 23 Apr 2019 23:52:22 +0200 Subject: [PATCH 11/19] Refactoring the createTeamArray-method --- pages/create.js | 51 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/pages/create.js b/pages/create.js index 799252e..0eb32b3 100644 --- a/pages/create.js +++ b/pages/create.js @@ -218,7 +218,7 @@ class CreateTournamentForm extends React.Component { 'description': this.state.description, 'public': this.state.public, 'group_stage': this.state.groupPhaseEnabled, - 'teams': this.createTeamArray(this.state.groupPhaseEnabled, this.state.groups, this.state.teams) + 'teams': createTeamArray(this.state.groupPhaseEnabled, this.state.groups, this.state.teams) }, () => { notify.show('Das Turnier wurde erfolgreich erstellt.', 'success', 5000); }, () => { @@ -226,24 +226,41 @@ class CreateTournamentForm extends React.Component { }); } - createTeamArray(/* boolean */ groupphase, /* String[][] */ groups, /* String[] */ teams) { - let result = []; +} - if(groupphase) { - for(let groupNumber = 0; groupNumber < groups.length; groupNumber++) { - for(let groupMember = 0; groupMember < groups[groupNumber].length; groupMember++) { - result[result.length] = { - 'name': groups[groupNumber][groupMember], - 'group': groupNumber - }; - } - } - } else { - for(let i = 0; i < teams.length; i++) { - result[i] = { 'name': teams[i] }; +/** + * This method creates an array of team objects that conform to the currently + * api specs available at https://apidoc.turnie.re/ + * + * @param {boolean} groupphase Whether a group phase is to be created + * @param {string[][]} groups The teams split into the groups that are + * to be used in the group phase of the tournament. Please note that + * according to the api every team can only occur once (not enforced + * by this method) and that every team from {@param teams} will have + * to be in one of the groups (also not enforced by this method, but + * might lead to inconsistencies) + * @param {string[]} teams An array containing all names of the teams + * that are to be created for the tournament + * @return {Object[]} an array of teams that can be directly sent to the + * backend + */ +function createTeamArray(groupphase, groups, teams) { + let result = []; + + if(groupphase) { + for(let groupNumber = 0; groupNumber < groups.length; groupNumber++) { + for(let groupMember = 0; groupMember < groups[groupNumber].length; groupMember++) { + result[result.length] = { + 'name': groups[groupNumber][groupMember], + 'group': groupNumber + }; } } - - return result; + } else { + for(let i = 0; i < teams.length; i++) { + result[i] = { 'name': teams[i] }; + } } + + return result; } From bf8a9431bfb42fadb564b7800c8681d4980303e9 Mon Sep 17 00:00:00 2001 From: Felix Hamme Date: Sat, 27 Apr 2019 13:27:11 +0200 Subject: [PATCH 12/19] Revert "Say goodbye to the user when he logs out" This reverts commit 7b38b503 --- js/api.js | 3 +-- js/components/Navigation.js | 17 ++++++----------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/js/api.js b/js/api.js index aae8724..d178f70 100644 --- a/js/api.js +++ b/js/api.js @@ -38,7 +38,7 @@ const actiontypes_userinfo = { const defaultstate_userinfo = { isSignedIn : false, - wasGreeted : true, + wasGreeted : false, username : null, error : false, errorMessages : [], @@ -261,7 +261,6 @@ const reducer_userinfo = (state = defaultstate_userinfo, action) => { case actiontypes_userinfo.CLEAR: return Object.assign({}, state, { isSignedIn : false, - wasGreeted : false, username : null, error : false, errorMessages : [], diff --git a/js/components/Navigation.js b/js/components/Navigation.js index cc4b07a..a1bbb77 100644 --- a/js/components/Navigation.js +++ b/js/components/Navigation.js @@ -14,7 +14,7 @@ import { connect } from 'react-redux'; import React from 'react'; import {greet, logout} from '../api'; -import {notify} from 'react-notify-toast'; +import {notify} from "react-notify-toast"; export class TurniereNavigation extends React.Component { @@ -70,12 +70,12 @@ class InvisibleLoginLogoutButtons extends React.Component { render() { const {isSignedIn, username, wasGreeted} = this.props; - if(isSignedIn) { - if (!wasGreeted) { - notify.show('Willkommen, ' + username + '!', 'success', 2500); - greet(); - } + if (isSignedIn && !wasGreeted) { + notify.show('Willkommen, ' + username + '!', 'success', 3000); + greet(); + } + if(isSignedIn) { return ( @@ -83,11 +83,6 @@ class InvisibleLoginLogoutButtons extends React.Component { ); } else { - if (!wasGreeted) { - notify.show('Du bist jetzt abgemeldet!', 'success', 2500); - greet(); - } - return ( From f243b69a5efd0920f412216f185966192a759fa3 Mon Sep 17 00:00:00 2001 From: Felix Hamme Date: Sat, 27 Apr 2019 13:27:26 +0200 Subject: [PATCH 13/19] Revert "Greet the user after login with a toast" This reverts commit b85ce62b --- js/api.js | 17 +---------------- js/components/Navigation.js | 14 ++++---------- 2 files changed, 5 insertions(+), 26 deletions(-) diff --git a/js/api.js b/js/api.js index d178f70..7877298 100644 --- a/js/api.js +++ b/js/api.js @@ -20,8 +20,7 @@ const actiontypes_userinfo = { 'LOGIN' : 'LOGIN', 'LOGIN_RESULT_SUCCESS' : 'LOGIN_RESULT_SUCCESS', 'LOGIN_RESULT_ERROR' : 'LOGIN_RESULT_ERROR', - 'GREET' : 'GREET', - + 'CLEAR_ERRORS' : 'CLEAR_ERRORS', 'LOGOUT' : 'LOGOUT', @@ -38,7 +37,6 @@ const actiontypes_userinfo = { const defaultstate_userinfo = { isSignedIn : false, - wasGreeted : false, username : null, error : false, errorMessages : [], @@ -216,7 +214,6 @@ const reducer_userinfo = (state = defaultstate_userinfo, action) => { case actiontypes_userinfo.LOGIN_RESULT_SUCCESS: return Object.assign({}, state, { isSignedIn : true, - wasGreeted : false, error : false, errorMessages : [], username : action.parameters.username, @@ -238,10 +235,6 @@ const reducer_userinfo = (state = defaultstate_userinfo, action) => { __store.dispatch({ type : actiontypes_userinfo.CLEAR }); }); return Object.assign({}, state, {}); - case actiontypes_userinfo.GREET: - return Object.assign({}, state, { - wasGreeted : true, - }); case actiontypes_userinfo.STORE_AUTH_HEADERS: return Object.assign({}, state, { accesstoken : action.parameters.accesstoken, @@ -403,14 +396,6 @@ export function login(email, password) { }); } -export function greet() { - __store.dispatch({ - type: actiontypes_userinfo.GREET, - parameters: {}, - state: __store.getState() - }); -} - export function logout() { __store.dispatch({ type : actiontypes_userinfo.LOGOUT, diff --git a/js/components/Navigation.js b/js/components/Navigation.js index a1bbb77..b9d0775 100644 --- a/js/components/Navigation.js +++ b/js/components/Navigation.js @@ -13,8 +13,7 @@ import { import { connect } from 'react-redux'; import React from 'react'; -import {greet, logout} from '../api'; -import {notify} from "react-notify-toast"; +import { logout } from '../api'; export class TurniereNavigation extends React.Component { @@ -68,12 +67,7 @@ function Betabadge() { class InvisibleLoginLogoutButtons extends React.Component { render() { - const {isSignedIn, username, wasGreeted} = this.props; - - if (isSignedIn && !wasGreeted) { - notify.show('Willkommen, ' + username + '!', 'success', 3000); - greet(); - } + const { isSignedIn, username } = this.props; if(isSignedIn) { return ( @@ -94,8 +88,8 @@ class InvisibleLoginLogoutButtons extends React.Component { } const mapStateToLoginLogoutButtonProperties = (state) => { - const {isSignedIn, username, wasGreeted} = state.userinfo; - return {isSignedIn, username, wasGreeted}; + const { isSignedIn, username } = state.userinfo; + return { isSignedIn, username }; }; const LoginLogoutButtons = connect( From 60c8bff55d36cf9ea19d289a879f44eb0e8af0c7 Mon Sep 17 00:00:00 2001 From: Felix Hamme Date: Sat, 27 Apr 2019 13:30:34 +0200 Subject: [PATCH 14/19] Greet the user with a toast notification when he logs in or out [now done with success callback methods] --- js/api.js | 13 ++++++++++--- js/components/Login.js | 3 ++- js/components/Navigation.js | 7 ++++++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/js/api.js b/js/api.js index 7877298..f5991ff 100644 --- a/js/api.js +++ b/js/api.js @@ -189,6 +189,7 @@ const reducer_userinfo = (state = defaultstate_userinfo, action) => { type : actiontypes_userinfo.LOGIN_RESULT_SUCCESS, parameters : { username : resp.data.username, + successCallback: action.parameters.successCallback } }); storeOptionalToken(resp); @@ -212,6 +213,7 @@ const reducer_userinfo = (state = defaultstate_userinfo, action) => { }); return Object.assign({}, state, {}); case actiontypes_userinfo.LOGIN_RESULT_SUCCESS: + action.parameters.successCallback(action.parameters.username); return Object.assign({}, state, { isSignedIn : true, error : false, @@ -230,6 +232,7 @@ const reducer_userinfo = (state = defaultstate_userinfo, action) => { }); case actiontypes_userinfo.LOGOUT: deleteRequest(action.state, '/users/sign_out').then(() => { + action.parameters.successCallback(); __store.dispatch({ type : actiontypes_userinfo.CLEAR }); }).catch(() => { __store.dispatch({ type : actiontypes_userinfo.CLEAR }); @@ -385,20 +388,24 @@ export function register(username, email, password) { }); } -export function login(email, password) { +export function login(email, password, successCallback) { __store.dispatch({ type: actiontypes_userinfo.LOGIN, parameters: { email: email, - password: password + password: password, + successCallback: successCallback }, state: __store.getState() }); } -export function logout() { +export function logout(successCallback) { __store.dispatch({ type : actiontypes_userinfo.LOGOUT, + parameters: { + successCallback: successCallback + }, state: __store.getState() }); } diff --git a/js/components/Login.js b/js/components/Login.js index 2d2b599..7a9430d 100644 --- a/js/components/Login.js +++ b/js/components/Login.js @@ -9,6 +9,7 @@ import { } from '../api'; import '../../static/css/errormessages.css'; +import {notify} from 'react-notify-toast'; export function Login(props) { return ( @@ -90,7 +91,7 @@ class LoginForm extends React.Component { tryLogin(event) { event.preventDefault(); - login(this.state.email, this.state.password); + login(this.state.email, this.state.password, (username) => notify.show('Willkommen, ' + username + '!', 'success', 2500)); } render() { diff --git a/js/components/Navigation.js b/js/components/Navigation.js index b9d0775..8c86753 100644 --- a/js/components/Navigation.js +++ b/js/components/Navigation.js @@ -14,6 +14,7 @@ import { connect } from 'react-redux'; import React from 'react'; import { logout } from '../api'; +import {notify} from 'react-notify-toast'; export class TurniereNavigation extends React.Component { @@ -66,6 +67,10 @@ function Betabadge() { class InvisibleLoginLogoutButtons extends React.Component { + logout(){ + logout(() => notify.show('Du bist jetzt abgemeldet.', 'success', 2500)); + } + render() { const { isSignedIn, username } = this.props; @@ -73,7 +78,7 @@ class InvisibleLoginLogoutButtons extends React.Component { return ( - + ); } else { From 6aeb97ae99868213c63139f4c2ebcd8cf6c70044 Mon Sep 17 00:00:00 2001 From: Felix Hamme Date: Sun, 28 Apr 2019 02:33:36 +0200 Subject: [PATCH 15/19] Remove unused code --- js/api.js | 21 --------------------- pages/private.js | 17 ----------------- 2 files changed, 38 deletions(-) diff --git a/js/api.js b/js/api.js index a4ab3d3..88a9795 100644 --- a/js/api.js +++ b/js/api.js @@ -274,27 +274,6 @@ const reducer_userinfo = (state = defaultstate_userinfo, action) => { expiry : null, uid : null }); - case actiontypes_userinfo.GET_TOURNAMENT_LIST: - getRequest(action.state, '/tournaments?type=' + action.parameters.type).then((resp) => { - __store.dispatch({ - type: actiontypes_tournamentinfo.GET_TOURNAMENT_LIST_SUCCESS, - parameters: resp - }); - storeOptionalToken(resp); - action.parameters.successCallback(); - }).catch((error) => { - __store.dispatch({ - type: actiontypes_tournamentinfo.GET_TOURNAMENT_LIST_ERROR, - parameters: {error: error} - }); - storeOptionalToken(error.response); - action.parameters.errorCallback(); - }); - return Object.assign({}, state, {}); - case actiontypes_userinfo.GET_TOURNAMENT_LIST_ERROR: - return Object.assign({}, state, {}); - case actiontypes_userinfo.GET_TOURNAMENT_LIST_SUCCESS: - return Object.assign({}, state, {...action.parameters}); default: return state; } }; diff --git a/pages/private.js b/pages/private.js index 8d732b1..1d6c20c 100644 --- a/pages/private.js +++ b/pages/private.js @@ -85,23 +85,6 @@ class PrivateTournamentsListCard extends React.Component { error }); }); - /* - getRequest(getState(), '/tournaments?type=private') - .then( - response => { - this.setState({ - isLoaded: true, - items: response.data - }); - }, - error => { - this.setState({ - isLoaded: true, - error - }); - } - ); - */ } render() { From dac0c3327254b723180ed5c38251c1e5e2804027 Mon Sep 17 00:00:00 2001 From: Felix Hamme Date: Sun, 28 Apr 2019 04:18:18 +0200 Subject: [PATCH 16/19] Implement page for displaying the list of private tournaments --- js/api.js | 10 ++-- js/components/TournamentList.js | 45 ++++++++++++++++++ pages/private.js | 82 +++++++++++---------------------- 3 files changed, 76 insertions(+), 61 deletions(-) create mode 100644 js/components/TournamentList.js diff --git a/js/api.js b/js/api.js index 88a9795..2e7d1a4 100644 --- a/js/api.js +++ b/js/api.js @@ -346,10 +346,10 @@ const reducer_tournamentlist = (state = defaultstate_tournamentlist, action) => getRequest(action.state, '/tournaments?type=' + action.parameters.type).then((resp) => { __store.dispatch({ type: actiontypes_tournamentlist.FETCH_SUCCESS, - parameters: resp + parameters: resp.data }); storeOptionalToken(resp); - action.parameters.successCallback(); + action.parameters.successCallback(resp.data); }).catch((error) => { __store.dispatch({ type: actiontypes_tournamentlist.FETCH_ERROR, @@ -360,7 +360,7 @@ const reducer_tournamentlist = (state = defaultstate_tournamentlist, action) => }); return state; case actiontypes_tournamentlist.FETCH_SUCCESS: - return Object.assign({}, state, {...action.parameters}); + return Object.assign({}, state, {tournaments: action.parameters}); case actiontypes_tournamentlist.FETCH_ERROR: return state; default: @@ -485,11 +485,11 @@ export function getState() { return __store.getState(); } -export function requestTournamentList(myType, successCallback, errorCallback) { +export function requestTournamentList(type, successCallback, errorCallback) { __store.dispatch({ type: actiontypes_tournamentlist.FETCH, parameters: { - type: myType, + type: type, successCallback: successCallback, errorCallback: errorCallback }, diff --git a/js/components/TournamentList.js b/js/components/TournamentList.js new file mode 100644 index 0000000..53055b8 --- /dev/null +++ b/js/components/TournamentList.js @@ -0,0 +1,45 @@ +import React from 'react'; +import {requestTournamentList} from '../api'; + +export default class TournamentList extends React.Component { + constructor(props) { + super(props); + this.state = { + error: null, + tournaments: [] + }; + } + + componentDidMount() { + requestTournamentList(this.props.type, tournaments => { + this.setState({ + tournaments: tournaments + }); + }, + error => { + this.setState({ + error + }); + }); + } + + render() { + if (this.state.tournaments.length === 0) { + return

        keine + Turniere vorhanden

        ; + } else { + return this.state.tournaments.map(item => ( + //The code should be item.code but the api just supports it this way by now + + )); + } + } +} + +function TournamentListEntry(props) { + return ( + + {props.name} + + ); +} \ No newline at end of file diff --git a/pages/private.js b/pages/private.js index 1d6c20c..63eaae0 100644 --- a/pages/private.js +++ b/pages/private.js @@ -8,9 +8,9 @@ import {TurniereNavigation} from '../js/components/Navigation'; import {Footer} from '../js/components/Footer'; import {Option, UserRestrictor} from '../js/components/UserRestrictor'; import {Login} from '../js/components/Login'; -import {requestTournamentList} from '../js/api'; import '../static/everypage.css'; +import TournamentList from '../js/components/TournamentList'; class PrivateTournamentsPage extends React.Component { @@ -25,9 +25,7 @@ class PrivateTournamentsPage extends React.Component { Private Turniere: turnie.re -
        - -
        +
@@ -49,65 +47,37 @@ class PrivateTournamentsPage extends React.Component { } } -function mapStateToCreatePageProperties(state) { +function mapStateToProperties(state) { const {isSignedIn} = state.userinfo; return {isSignedIn}; } -const CreatePage = connect( - mapStateToCreatePageProperties, +const PrivateTournamentListPage = connect( + mapStateToProperties, )(PrivateTournamentsPage); -export default CreatePage; +export default PrivateTournamentListPage; - - -class PrivateTournamentsListCard extends React.Component { - constructor(props) { - super(props); - this.state = { - error: null, - isLoaded: false, - items: [] - }; - } - - componentDidMount() { - requestTournamentList('private', response => { - this.setState({ - isLoaded: true, - items: response.data - }); - }, - error => { - this.setState({ - isLoaded: true, - error - }); - }); - } - - render() { - return ( - - - -

Private Turniere

- {this.state.items.map(item => ( - //The code should be item.code but the api just supports it this way by now - - ))} -
-
-
- ); - } +function PrivateTournamentsPageContent() { + return (); } -function TournamentListEntry(props) { - return ( - - {props.name} - - ); +class PrivateTournamentsCard extends React.Component { + render() { + return ( + + +

Private Turniere

+ +
+
+ ); + } } \ No newline at end of file From 1af7ee413febea407b32f6392aa0f465320d6ef1 Mon Sep 17 00:00:00 2001 From: Felix Hamme Date: Sun, 28 Apr 2019 04:21:01 +0200 Subject: [PATCH 17/19] Change the list of public tournaments to share some redundant code with the list of private tournaments --- pages/list.js | 99 ++++++++++++++++++++------------------------------- 1 file changed, 38 insertions(+), 61 deletions(-) diff --git a/pages/list.js b/pages/list.js index 768b105..1cf8496 100644 --- a/pages/list.js +++ b/pages/list.js @@ -1,22 +1,16 @@ -import Head from 'next/head'; -import React from 'react'; -import { - Card, - CardBody, - Container -} from 'reactstrap'; +import Head from 'next/head'; +import React from 'react'; +import {Card, CardBody, Container} from 'reactstrap'; -import { TurniereNavigation } from '../js/components/Navigation'; -import { Footer } from '../js/components/Footer'; -import { - getRequest, - getState -} from '../js/api'; +import {TurniereNavigation} from '../js/components/Navigation'; +import {Footer} from '../js/components/Footer'; import '../static/everypage.css'; +import TournamentList from '../js/components/TournamentList'; +import {connect} from 'react-redux'; + +export default class PublicTournamentsPage extends React.Component { -export default class ListPage extends React.Component { - render() { return (
@@ -25,7 +19,7 @@ export default class ListPage extends React.Component {
- +
@@ -33,55 +27,38 @@ export default class ListPage extends React.Component { } } -class TournamentList extends React.Component { - constructor(props) { - super(props); - this.state = { - error: null, - isLoaded: false, - items: [] - }; - } +function mapStateToProperties(state) { + const {isSignedIn} = state.userinfo; + return {isSignedIn}; +} - componentDidMount() { - getRequest(getState(), '/tournaments?type=public') - .then( - response => { - this.setState({ - isLoaded: true, - items: response.data - }); - }, - error => { - this.setState({ - isLoaded: true, - error - }); - } - ); - } +const PublicTournamentPageContent = connect( + mapStateToProperties, +)(PublicTournaments); - render() { - return ( - - - -

Öffentliche Turniere

- {this.state.items.map(item => ( - //The code should be item.code but the api just supports it this way by now - - ))} -
-
+function PublicTournaments(props) { + if (props.isSignedIn) { + return (
+ + - ); + + zu den privaten Turnieren + +
); + + } else { + return ( + + ); } } -function TournamentListEntry(props) { - return ( - - {props.name} - - ); +function PublicTournamentsCard() { + return ( + +

Öffentliche Turniere

+ +
+
); } From fd3085e1f1a5e8c4a6bc3eb252b6ecf81b57bac1 Mon Sep 17 00:00:00 2001 From: Felix Hamme Date: Sun, 28 Apr 2019 04:27:41 +0200 Subject: [PATCH 18/19] Change code formatting to satisfy Hound --- js/api.js | 44 ++++++++++++++++----------------- js/components/TournamentList.js | 16 ++++++------ 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/js/api.js b/js/api.js index b0a94f9..6a70330 100644 --- a/js/api.js +++ b/js/api.js @@ -345,29 +345,29 @@ const reducer_tournamentinfo = (state = defaultstate_tournamentinfo, action) => const reducer_tournamentlist = (state = defaultstate_tournamentlist, action) => { switch (action.type) { - case actiontypes_tournamentlist.FETCH: - getRequest(action.state, '/tournaments?type=' + action.parameters.type).then((resp) => { - __store.dispatch({ - type: actiontypes_tournamentlist.FETCH_SUCCESS, - parameters: resp.data - }); - storeOptionalToken(resp); - action.parameters.successCallback(resp.data); - }).catch((error) => { - __store.dispatch({ - type: actiontypes_tournamentlist.FETCH_ERROR, - parameters: {error: error} - }); - storeOptionalToken(error.response); - action.parameters.errorCallback(); + case actiontypes_tournamentlist.FETCH: + getRequest(action.state, '/tournaments?type=' + action.parameters.type).then((resp) => { + __store.dispatch({ + type: actiontypes_tournamentlist.FETCH_SUCCESS, + parameters: resp.data }); - return state; - case actiontypes_tournamentlist.FETCH_SUCCESS: - return Object.assign({}, state, {tournaments: action.parameters}); - case actiontypes_tournamentlist.FETCH_ERROR: - return state; - default: - return state; + storeOptionalToken(resp); + action.parameters.successCallback(resp.data); + }).catch((error) => { + __store.dispatch({ + type: actiontypes_tournamentlist.FETCH_ERROR, + parameters: {error: error} + }); + storeOptionalToken(error.response); + action.parameters.errorCallback(); + }); + return state; + case actiontypes_tournamentlist.FETCH_SUCCESS: + return Object.assign({}, state, {tournaments: action.parameters}); + case actiontypes_tournamentlist.FETCH_ERROR: + return state; + default: + return state; } }; diff --git a/js/components/TournamentList.js b/js/components/TournamentList.js index 53055b8..30ff6e3 100644 --- a/js/components/TournamentList.js +++ b/js/components/TournamentList.js @@ -12,15 +12,15 @@ export default class TournamentList extends React.Component { componentDidMount() { requestTournamentList(this.props.type, tournaments => { - this.setState({ - tournaments: tournaments - }); - }, - error => { - this.setState({ - error - }); + this.setState({ + tournaments: tournaments }); + }, + error => { + this.setState({ + error + }); + }); } render() { From a1828c30c3e0e7526a6f0c3b0a042582e899e9f6 Mon Sep 17 00:00:00 2001 From: Felix Hamme Date: Wed, 1 May 2019 15:53:22 +0200 Subject: [PATCH 19/19] Review changes: code cleanup --- js/api.js | 11 +++-------- js/components/TournamentList.js | 8 +------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/js/api.js b/js/api.js index 6a70330..9808ca7 100644 --- a/js/api.js +++ b/js/api.js @@ -75,7 +75,6 @@ const defaultstate_tournamentinfo = { const actiontypes_tournamentlist = { 'FETCH': 'FETCH', 'FETCH_SUCCESS': 'FETCH_SUCCESS', - 'FETCH_ERROR': 'FETCH_ERROR', 'REHYDRATE': 'REHYDRATE' }; @@ -354,18 +353,14 @@ const reducer_tournamentlist = (state = defaultstate_tournamentlist, action) => storeOptionalToken(resp); action.parameters.successCallback(resp.data); }).catch((error) => { - __store.dispatch({ - type: actiontypes_tournamentlist.FETCH_ERROR, - parameters: {error: error} - }); - storeOptionalToken(error.response); + if(error.response) { + storeOptionalToken(error.response); + } action.parameters.errorCallback(); }); return state; case actiontypes_tournamentlist.FETCH_SUCCESS: return Object.assign({}, state, {tournaments: action.parameters}); - case actiontypes_tournamentlist.FETCH_ERROR: - return state; default: return state; } diff --git a/js/components/TournamentList.js b/js/components/TournamentList.js index 30ff6e3..9a0ac00 100644 --- a/js/components/TournamentList.js +++ b/js/components/TournamentList.js @@ -5,7 +5,6 @@ export default class TournamentList extends React.Component { constructor(props) { super(props); this.state = { - error: null, tournaments: [] }; } @@ -15,12 +14,7 @@ export default class TournamentList extends React.Component { this.setState({ tournaments: tournaments }); - }, - error => { - this.setState({ - error - }); - }); + }, () => {}); } render() {