Merge pull request #5 from turniere/ticket/TURNIERE-113

Make "Edit Tournament"-Page with working editing of team names
This commit is contained in:
Jonny 2018-12-13 08:57:43 +01:00 committed by GitHub
commit e7004de3c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 792 additions and 207 deletions

149
js/api.js
View File

@ -40,6 +40,28 @@ const defaultstate_userinfo = {
uid : null uid : null
}; };
const actiontypes_tournamentinfo = {
'REQUEST_TOURNAMENT' : 'REQUEST_TOURNAMENT',
'REQUEST_TOURNAMENT_SUCCESS' : 'REQUEST_TOURNAMENT_SUCCESS',
'MODIFY_TOURNAMENT' : 'MODIFY_TOURNAMENT',
'MODIFY_TOURNAMENT_SUCCESS' : 'MODIFY_TOURNAMENT_SUCCESS',
'MODIFY_TOURNAMENT_ERROR' : 'MODIFY_TOURNAMENT_ERROR',
'REHYDRATE' : 'TOURNAMENTINFO_REHYDRATE',
'CLEAR' : 'TOURNAMENTINFO_CLEAR',
};
const defaultstate_tournamentinfo = {
code : '',
description : '',
id : -1,
name : '',
isPublic : '',
stages: [],
teams : []
};
export function postRequest(state, url, data) { export function postRequest(state, url, data) {
return axios.post(api_url + url, data, { return axios.post(api_url + url, data, {
headers : generateHeaders(state) headers : generateHeaders(state)
@ -58,12 +80,18 @@ export function deleteRequest(state, url) {
}); });
} }
export function patchRequest(state, url, data) {
return axios.patch(api_url + url, data, {
headers : generateHeaders(state)
});
}
function generateHeaders(state) { function generateHeaders(state) {
if(state.isSignedIn) { if(state.userinfo.isSignedIn) {
return { return {
'access-token' : state.accesstoken, 'access-token' : state.userinfo.accesstoken,
'client' : state.client, 'client' : state.userinfo.client,
'uid' : state.uid 'uid' : state.userinfo.uid
}; };
} else { } else {
return {}; return {};
@ -102,7 +130,7 @@ function checkForAuthenticationHeaders(response) {
const reducer_userinfo = (state = defaultstate_userinfo, action) => { const reducer_userinfo = (state = defaultstate_userinfo, action) => {
switch(action.type) { switch(action.type) {
case actiontypes_userinfo.REGISTER: case actiontypes_userinfo.REGISTER:
postRequest(state, '/users', { postRequest(action.state, '/users', {
'username' : action.parameters.username, 'username' : action.parameters.username,
'email' : action.parameters.email, 'email' : action.parameters.email,
'password' : action.parameters.password 'password' : action.parameters.password
@ -143,7 +171,7 @@ const reducer_userinfo = (state = defaultstate_userinfo, action) => {
errorMessages : action.parameters.errorMessages errorMessages : action.parameters.errorMessages
}); });
case actiontypes_userinfo.LOGIN: case actiontypes_userinfo.LOGIN:
postRequest(state, '/users/sign_in', { postRequest(action.state, '/users/sign_in', {
email : action.parameters.email, email : action.parameters.email,
password : action.parameters.password password : action.parameters.password
}).then((resp) => { }).then((resp) => {
@ -186,7 +214,7 @@ const reducer_userinfo = (state = defaultstate_userinfo, action) => {
errorMessages : action.parameters.errorMessages errorMessages : action.parameters.errorMessages
}); });
case actiontypes_userinfo.LOGOUT: case actiontypes_userinfo.LOGOUT:
deleteRequest(state, '/users/sign_out').then(() => { deleteRequest(action.state, '/users/sign_out').then(() => {
__store.dispatch({ type : actiontypes_userinfo.CLEAR }); __store.dispatch({ type : actiontypes_userinfo.CLEAR });
}).catch(() => { }).catch(() => {
__store.dispatch({ type : actiontypes_userinfo.CLEAR }); __store.dispatch({ type : actiontypes_userinfo.CLEAR });
@ -200,7 +228,7 @@ const reducer_userinfo = (state = defaultstate_userinfo, action) => {
uid : action.parameters.uid uid : action.parameters.uid
}); });
case actiontypes_userinfo.VERIFY_CREDENTIALS: case actiontypes_userinfo.VERIFY_CREDENTIALS:
getRequest(state, '/users/validate_token').then((resp) => { getRequest(action.state, '/users/validate_token').then((resp) => {
storeOptionalToken(resp); storeOptionalToken(resp);
}).catch(() => { }).catch(() => {
__store.dispatch({ type: actiontypes_userinfo.CLEAR }); __store.dispatch({ type: actiontypes_userinfo.CLEAR });
@ -224,12 +252,68 @@ const reducer_userinfo = (state = defaultstate_userinfo, action) => {
} }
}; };
const reducer_tournamentinfo = (state = defaultstate_tournamentinfo, action) => {
switch(action.type) {
case actiontypes_tournamentinfo.REQUEST_TOURNAMENT:
getRequest(action.state, '/tournaments/' + action.parameters.code).then((resp) => {
__store.dispatch({
type: actiontypes_tournamentinfo.REQUEST_TOURNAMENT_SUCCESS,
parameters: resp.data
});
storeOptionalToken(resp);
action.parameters.successCallback();
}).catch(() => {
action.parameters.errorCallback();
});
return Object.assign({}, state, {});
case actiontypes_tournamentinfo.REQUEST_TOURNAMENT_SUCCESS:
return Object.assign({}, state, {
code : action.parameters.code,
description : action.parameters.description,
id : action.parameters.id,
name : action.parameters.name,
isPublic : action.parameters.public,
stages: action.parameters.stages,
teams : action.parameters.teams
});
case actiontypes_tournamentinfo.MODIFY_TOURNAMENT:
patchRequest(action.state, '/teams/' + action.parameters.teamid, {
name: action.parameters.name
}).then((resp) => {
storeOptionalToken(resp);
action.parameters.onSuccess();
}).catch((error) => {
if(error.response) {
storeOptionalToken(error.response);
}
action.parameters.onError();
});
return Object.assign({}, state, {});
case actiontypes_tournamentinfo.MODIFY_TOURNAMENT_SUCCESS:
return Object.assign({}, state, {});
case actiontypes_tournamentinfo.MODIFY_TOURNAMENT_ERROR:
return Object.assign({}, state, {});
case actiontypes_tournamentinfo.REHYDRATE:
return Object.assign({}, state, {});
case actiontypes_tournamentinfo.CLEAR:
return Object.assign({}, state, {});
default: return state;
}
};
const reducers = { const reducers = {
userinfo: reducer_userinfo userinfo: reducer_userinfo,
tournamentinfo: reducer_tournamentinfo
}; };
const default_applicationstate = { const default_applicationstate = {
userinfo : defaultstate_userinfo userinfo : defaultstate_userinfo,
tournamentinfo: defaultstate_tournamentinfo
}; };
var __store; var __store;
@ -250,7 +334,10 @@ export function verifyCredentials() {
rehydrateApplicationState(); rehydrateApplicationState();
if(__store.getState().userinfo.isSignedIn) { if(__store.getState().userinfo.isSignedIn) {
__store.dispatch({ type: actiontypes_userinfo.VERIFY_CREDENTIALS }); __store.dispatch({
type: actiontypes_userinfo.VERIFY_CREDENTIALS,
state: __store.getState()
});
} }
} }
@ -261,7 +348,8 @@ export function register(username, email, password) {
username: username, username: username,
email: email, email: email,
password: password password: password
} },
state: __store.getState()
}); });
} }
@ -271,12 +359,41 @@ export function login(email, password) {
parameters: { parameters: {
email: email, email: email,
password: password password: password
} },
state: __store.getState()
}); });
} }
export function logout() { export function logout() {
__store.dispatch({ type : actiontypes_userinfo.LOGOUT }); __store.dispatch({
type : actiontypes_userinfo.LOGOUT,
state: __store.getState()
});
}
export function requestTournament(code, successCallback, errorCallback) {
__store.dispatch({
type: actiontypes_tournamentinfo.REQUEST_TOURNAMENT,
parameters: {
code: code,
successCallback: successCallback,
errorCallback: errorCallback
},
state: __store.getState()
});
}
export function updateTeamName(team, successCB, errorCB) {
__store.dispatch({
type: actiontypes_tournamentinfo.MODIFY_TOURNAMENT,
parameters: {
teamid: team.id,
name: team.name,
onSuccess : successCB,
onError : errorCB
},
state: __store.getState()
});
} }
export function getState() { export function getState() {
@ -293,6 +410,10 @@ function rehydrateApplicationState() {
type : actiontypes_userinfo.REHYDRATE, type : actiontypes_userinfo.REHYDRATE,
parameters : Object.assign({}, persistedState.userinfo, {}) parameters : Object.assign({}, persistedState.userinfo, {})
}); });
__store.dispatch({
type : actiontypes_tournamentinfo.REHYDRATE,
parameters : Object.assign({}, persistedState.tournamentinfo, {})
});
} }
} }

View File

@ -0,0 +1,86 @@
import Head from 'next/head';
import React from 'react';
import {Footer, TurniereNavigation} from '../CommonComponents';
import 'bootstrap/dist/css/bootstrap.min.css';
import {Container} from 'reactstrap';
import '../../static/everypage.css';
import '../../static/css/error.css';
export class ErrorPageComponent extends React.Component {
static getInitialProps({ statusCode }) {
return { statusCode };
}
render() {
return (
<div>
<Head>
<title>turnie.re - Error {this.props.statusCode}</title>
</Head>
<TurniereNavigation/>
<ErrorPage statusCode={this.props.statusCode}/>
<Footer/>
</div>
);
}
}
function ErrorPage(props){
return (
<Container className="mb-5">
<div className="row mb-5">
<div className="col-lg text-center">
<img src="/static/images/logo-questionmark.png" className="w-75 img-fluid"/>
</div>
<div className="col-lg error-code-box">
<h1 className="custom-font py-5">{props.statusCode}</h1>
</div>
</div>
<ErrorMessage code={props.statusCode}/>
</Container>
);
}
function ErrorMessage(props) {
switch (props.code) {
case 400:
return (<div className="running-text">
<h2>Deine Anfrage ist fehlerhaft.</h2>
<p>
Wir empfehlen, die eingegebene Seite über die <a href="/">Startseite</a> zu suchen.
</p>
</div>);
case 403:
return (<div className="running-text">
<h2>Du bist nicht autorisiert, diese Seite aufzurufen.</h2>
<p>
Bitte stelle sicher, dass Du angemeldet bist und auf dieses Turnier oder dieses Match zugreifen darfst.
</p>
<p>
Wir empfehlen, die eingegebene Seite über die <a href="/">Startseite</a> zu suchen.
</p>
</div>);
case 404:
return (<div className="running-text">
<h2>Die aufgerufene Seite wurde leider nicht gefunden.</h2>
<p>
Entweder hast Du dich vertippt, oder die gesuchte Seite gibt es nicht.
</p>
<p>
Wir empfehlen, die eingegebene Seite über die <a href="/">Startseite</a> zu suchen.
</p>
</div>);
case 500:
return (<div className="running-text">
<h2>Diese Seite funktioniert nicht.</h2>
<p>
turnie.re kann Deine Anfrage im Moment nicht verarbeiten. Bitte versuche es später erneut.
</p>
</div>);
default:
return (<div>
<h2>Ein unbekannter Fehler ist aufgetreten.</h2>
</div>);
}
}

View File

@ -20,6 +20,7 @@
"next": "^7.0.2", "next": "^7.0.2",
"react": "^16.6.1", "react": "^16.6.1",
"react-dom": "^16.6.1", "react-dom": "^16.6.1",
"react-notify-toast": "^0.5.0",
"react-redux": "^5.1.1", "react-redux": "^5.1.1",
"reactstrap": "^6.5.0", "reactstrap": "^6.5.0",
"redux": "^4.0.1", "redux": "^4.0.1",

View File

@ -2,18 +2,15 @@ import App, {Container} from 'next/app';
import React from 'react'; import React from 'react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import withReduxStore from '../js/redux/reduxStoreBinder'; import withReduxStore from '../js/redux/reduxStoreBinder';
import { verifyCredentials } from '../js/api'; import Notifications from 'react-notify-toast';
class TurniereApp extends App { class TurniereApp extends App {
componentDidMount() {
verifyCredentials();
}
render () { render () {
const {Component, pageProps, reduxStore} = this.props; const {Component, pageProps, reduxStore} = this.props;
return ( return (
<Container> <Container>
<Notifications />
<Provider store={reduxStore}> <Provider store={reduxStore}>
<Component {...pageProps} /> <Component {...pageProps} />
</Provider> </Provider>

View File

@ -1,10 +1,9 @@
import Head from 'next/head'; import { ErrorPageComponent } from '../js/components/ErrorComponents.js';
import React from 'react'; import React from 'react';
import {Footer, TurniereNavigation} from '../js/CommonComponents';
import 'bootstrap/dist/css/bootstrap.min.css'; import {
import {Container} from 'reactstrap'; verifyCredentials
import '../static/everypage.css'; } from '../js/api';
import '../static/css/error.css';
export default class Error extends React.Component { export default class Error extends React.Component {
static getInitialProps({ res, err }) { static getInitialProps({ res, err }) {
@ -12,75 +11,13 @@ export default class Error extends React.Component {
return { statusCode }; return { statusCode };
} }
componentDidMount() {
verifyCredentials();
}
render() { render() {
return ( return (
<div> <ErrorPageComponent statusCode={this.props.statusCode}/>
<Head>
<title>turnie.re - Error {this.props.statusCode}</title>
</Head>
<TurniereNavigation/>
<ErrorPage statusCode={this.props.statusCode}/>
<Footer/>
</div>
); );
} }
} }
function ErrorPage(props){
return (
<Container className="mb-5">
<div className="row mb-5">
<div className="col-lg text-center">
<img src="/static/images/logo-questionmark.png" className="w-75 img-fluid"/>
</div>
<div className="col-lg error-code-box">
<h1 className="custom-font py-5">{props.statusCode}</h1>
</div>
</div>
<ErrorMessage code={props.statusCode}/>
</Container>
);
}
function ErrorMessage(props) {
switch (props.code) {
case 400:
return (<div className="running-text">
<h2>Deine Anfrage ist fehlerhaft.</h2>
<p>
Wir empfehlen, die eingegebene Seite über die <a href="/">Startseite</a> zu suchen.
</p>
</div>);
case 403:
return (<div className="running-text">
<h2>Du bist nicht autorisiert, diese Seite aufzurufen.</h2>
<p>
Bitte stelle sicher, dass Du angemeldet bist und auf dieses Turnier oder dieses Match zugreifen darfst.
</p>
<p>
Wir empfehlen, die eingegebene Seite über die <a href="/">Startseite</a> zu suchen.
</p>
</div>);
case 404:
return (<div className="running-text">
<h2>Die aufgerufene Seite wurde leider nicht gefunden.</h2>
<p>
Entweder hast Du dich vertippt, oder die gesuchte Seite gibt es nicht.
</p>
<p>
Wir empfehlen, die eingegebene Seite über die <a href="/">Startseite</a> zu suchen.
</p>
</div>);
case 500:
return (<div className="running-text">
<h2>Diese Seite funktioniert nicht.</h2>
<p>
turnie.re kann Deine Anfrage im Moment nicht verarbeiten. Bitte versuche es später erneut.
</p>
</div>);
default:
return (<div>
<h2>Ein unbekannter Fehler ist aufgetreten.</h2>
</div>);
}
}

View File

@ -16,20 +16,34 @@ import {
Label Label
} from 'reactstrap'; } from 'reactstrap';
import {
verifyCredentials
} from '../js/api';
import EditableStringList from '../js/EditableStringList'; import EditableStringList from '../js/EditableStringList';
export default () => ( export default class CreatePage extends React.Component {
<div className="main generic-fullpage-bg">
<Head> componentDidMount() {
<title>Turnier erstellen: turnie.re</title> verifyCredentials();
</Head> }
<TurniereNavigation/>
<div> render() {
<CreateTournamentCard/> return (
</div> <div className="main generic-fullpage-bg">
<Footer/> <Head>
</div> <title>Turnier erstellen: turnie.re</title>
); </Head>
<TurniereNavigation/>
<div>
<CreateTournamentCard/>
</div>
<Footer/>
</div>
);
}
}
function CreateTournamentCard() { function CreateTournamentCard() {
return ( return (

View File

@ -5,6 +5,10 @@ import 'bootstrap/dist/css/bootstrap.min.css';
import { BigImage, Footer, TurniereNavigation } from '../js/CommonComponents.js'; import { BigImage, Footer, TurniereNavigation } from '../js/CommonComponents.js';
import '../static/everypage.css'; import '../static/everypage.css';
import {
verifyCredentials
} from '../js/api';
function Main() { function Main() {
return ( return (
<div className="main"> <div className="main">
@ -216,14 +220,23 @@ function TournamentFaq() {
); );
} }
export default () => ( export default class FaqPage extends React.Component {
<div>
<Head> componentDidMount() {
<title>FAQ: turnie.re</title> verifyCredentials();
</Head> }
<TurniereNavigation/>
<BigImage text="FAQ"/> render() {
<Main/> return (
<Footer/> <div>
</div> <Head>
); <title>FAQ: turnie.re</title>
</Head>
<TurniereNavigation/>
<BigImage text="FAQ"/>
<Main/>
<Footer/>
</div>
);
}
}

View File

@ -5,6 +5,10 @@ import 'bootstrap/dist/css/bootstrap.min.css';
import {BigImage, Footer, TurniereNavigation} from '../js/CommonComponents.js'; import {BigImage, Footer, TurniereNavigation} from '../js/CommonComponents.js';
import '../static/everypage.css'; import '../static/everypage.css';
import {
verifyCredentials
} from '../js/api';
function Main() { function Main() {
return ( return (
<div className="main running-text"> <div className="main running-text">
@ -69,14 +73,23 @@ function ImprintText(){
} }
export default () => ( export default class ImprintPage extends React.Component {
<div>
<Head> componentDidMount() {
<title>Impressum: turnie.re</title> verifyCredentials();
</Head> }
<TurniereNavigation/>
<BigImage text="Impressum / Haftungs&shy;ausschluss"/> render() {
<Main/> return (
<Footer/> <div>
</div> <Head>
); <title>Impressum: turnie.re</title>
</Head>
<TurniereNavigation/>
<BigImage text="Impressum / Haftungs&shy;ausschluss"/>
<Main/>
<Footer/>
</div>
);
}
}

View File

@ -12,6 +12,10 @@ import '../static/css/index.css';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import {
verifyCredentials
} from '../js/api';
function Main() { function Main() {
return ( return (
<div className="main"> <div className="main">
@ -114,7 +118,7 @@ function PromotedLinkTournamentCode() {
<CardBody className="row"> <CardBody className="row">
<form id="turniercode-form" className="col-lg-4" action="/t" method="get"> <form id="turniercode-form" className="col-lg-4" action="/t" method="get">
<input className="form-control" type="search" name="code" placeholder="Turnier-Code"/> <input className="form-control" type="search" name="code" placeholder="Turnier-Code"/>
<button className="btn btn-outline-success w-100 my-2" type="submit">Turnier-Code öffnen</button> <Button className="btn btn-outline-success w-100 my-2" type="submit">Turnier-Code öffnen</Button>
</form> </form>
<div className="col-lg-8"> <div className="col-lg-8">
<p>Gib hier einen Turnier Code ein, um direkt zum entsprechenden Turnier zu gelangen.</p> <p>Gib hier einen Turnier Code ein, um direkt zum entsprechenden Turnier zu gelangen.</p>
@ -165,6 +169,11 @@ function PromotedLinkCreateTournament() {
class Index extends React.Component { class Index extends React.Component {
componentDidMount() {
verifyCredentials();
}
render () { render () {
return ( return (
<div> <div>

View File

@ -1,22 +1,37 @@
import Head from 'next/head'; import Head from 'next/head';
import '../static/everypage.css';
import { Footer, TurniereNavigation } from '../js/CommonComponents';
import React from 'react'; import React from 'react';
import { Card, CardBody, Container } from 'reactstrap'; import { Card, CardBody, Container } from 'reactstrap';
import { getRequest, getState } from '../js/api';
export default () => ( import { Footer, TurniereNavigation } from '../js/CommonComponents';
<div className="main generic-fullpage-bg"> import {
<Head> getRequest,
<title>Öffentliche Turniere: turnie.re</title> getState,
</Head> verifyCredentials
<TurniereNavigation/> } from '../js/api';
<div>
<TournamentList/> import '../static/everypage.css';
</div>
<Footer/> export default class ListPage extends React.Component {
</div>
); componentDidMount() {
verifyCredentials();
}
render() {
return (
<div className="main generic-fullpage-bg">
<Head>
<title>Öffentliche Turniere: turnie.re</title>
</Head>
<TurniereNavigation/>
<div>
<TournamentList/>
</div>
<Footer/>
</div>
);
}
}
class TournamentList extends React.Component { class TournamentList extends React.Component {
constructor(props) { constructor(props) {

View File

@ -6,18 +6,31 @@ import { Button, Card, CardBody, Container, Form, FormGroup, Input, Label } from
import { login } from '../js/api'; import { login } from '../js/api';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
export default () => ( import {
<div className="main generic-fullpage-bg"> verifyCredentials
<Head> } from '../js/api';
<title>Login: turnie.re</title>
</Head> export default class LoginPage extends React.Component {
<TurniereNavigation/>
<div> componentDidMount() {
<Login/> verifyCredentials();
</div> }
<Footer/>
</div> render() {
); return (
<div className="main generic-fullpage-bg">
<Head>
<title>Login: turnie.re</title>
</Head>
<TurniereNavigation/>
<div>
<Login/>
</div>
<Footer/>
</div>
);
}
}
function Login() { function Login() {
return ( return (

View File

@ -5,6 +5,10 @@ import 'bootstrap/dist/css/bootstrap.min.css';
import { BigImage, Footer, TurniereNavigation } from '../js/CommonComponents.js'; import { BigImage, Footer, TurniereNavigation } from '../js/CommonComponents.js';
import '../static/everypage.css'; import '../static/everypage.css';
import {
verifyCredentials
} from '../js/api';
function Main() { function Main() {
return ( return (
<div className="main running-text"> <div className="main running-text">
@ -488,14 +492,23 @@ function PrivacyText(){
} }
export default () => ( export default class PrivacyPage extends React.Component {
<div>
<Head> componentDidMount() {
<title>Datenschutzerklärung: turnie.re</title> verifyCredentials();
</Head> }
<TurniereNavigation/>
<BigImage text="Datenschutzerklärung"/> render() {
<Main/> return (
<Footer/> <div>
</div> <Head>
); <title>Datenschutzerklärung: turnie.re</title>
</Head>
<TurniereNavigation/>
<BigImage text="Datenschutzerklärung"/>
<Main/>
<Footer/>
</div>
);
}
}

View File

@ -6,19 +6,32 @@ import { Button, Card, CardBody, Container, Form, FormGroup, FormText, Input, La
import { register } from '../js/api'; import { register } from '../js/api';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
export default () => ( import {
<div className="main generic-fullpage-bg"> verifyCredentials
<Head> } from '../js/api';
<title>Registrieren: turnie.re</title>
</Head> export default class RegisterPage extends React.Component {
<TurniereNavigation/>
<div> componentDidMount() {
<Register/> verifyCredentials();
<AccountRequirementMarketing/> }
</div>
<Footer/> render() {
</div> return (
); <div className="main generic-fullpage-bg">
<Head>
<title>Registrieren: turnie.re</title>
</Head>
<TurniereNavigation/>
<div>
<Register/>
<AccountRequirementMarketing/>
</div>
<Footer/>
</div>
);
}
}
function Register() { function Register() {
return ( return (

290
pages/tournament-edit.js Normal file
View File

@ -0,0 +1,290 @@
import Head from 'next/head';
import React from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import { connect } from 'react-redux';
import { notify } from 'react-notify-toast';
import { requestTournament } from '../js/api';
import { BigImage, Footer, TurniereNavigation } from '../js/CommonComponents.js';
import { ErrorPageComponent } from '../js/components/ErrorComponents.js';
import {
Container,
Button,
Card,
CardBody,
Table
} from 'reactstrap';
import {
verifyCredentials,
updateTeamName
} from '../js/api';
import '../static/everypage.css';
import '../static/css/index.css';
class EditTournamentPage extends React.Component {
static async getInitialProps({query}) {
return {query};
}
constructor(props) {
super(props);
this.state = {
validCode: true
};
}
componentDidMount() {
verifyCredentials();
requestTournament(this.props.query.code, () => {
this.setState({ validCode: true });
this._edittournamentcontent.notifyOfContentUpdate();
}, () => {
this.setState({ validCode: false });
});
}
render() {
const { validCode } = this.state;
const { name } = this.props;
if(validCode) {
return (
<div className='pb-5'>
<Head>
<title>Turnie.re - Turnier bearbeiten</title>
</Head>
<TurniereNavigation/>
<BigImage text={ name }/>
<EditTournamentContent ref={(edittournamentcontent) => { this._edittournamentcontent = edittournamentcontent; }}/>
<Footer/>
</div>
);
} else {
return (
<ErrorPageComponent statusCode={ 404 }/>
);
}
}
}
function mapStateToTournamentInfo(state) {
const { name } = state.tournamentinfo;
return { name };
}
export default connect(
mapStateToTournamentInfo
)(EditTournamentPage);
class EditTournamentContent extends React.Component {
render() {
return (
<div className='mb-5'>
<ReturnToTournamentButton/>
<EditTournamentPropertiesField ref={(field) => { this._edittournamentpropertiesfield = field; }}/>
<EditTeamField ref={(field) => { this._editteamfield = field; }}/>
</div>
);
}
notifyOfContentUpdate() {
this._edittournamentpropertiesfield.notifyOfContentUpdate();
this._editteamfield.notifyOfContentUpdate();
}
}
function ReturnToTournamentButton() {
return (
<Container className="px-0">
<Button color="secondary" className="mb-5 w-100" href="./">Zurück zum Turnier</Button>
</Container>
);
}
class EditTournamentPropertiesField extends React.Component {
render() {
return (
<Card className="container">
<CardBody>
<h2>Turnier-Eigenschaften ändern</h2>
<VisibleEditTournamentForm ref={(form) => { this._visibleedittournamentform = form; }}/>
</CardBody>
</Card>
);
}
notifyOfContentUpdate() {
this._visibleedittournamentform.getWrappedInstance().notifyOfContentUpdate();
}
}
class EditTournamentForm extends React.Component {
constructor(props) {
super(props);
this.state = {
name : '',
description : '',
isPublic : false
};
}
render() {
const { name, description, isPublic } = this.state;
return (
<div>
<div className="form-group">
<label htmlFor="name">Turnier-Name</label>
<input className="form-control" type="text" name="name" id="edittournament-textfield-name" value={ name } placeholder={ name } onChange={ this.handleNameInput.bind(this) } />
</div>
<div className="form-group">
<label htmlFor="name">Turnier-Beschreibung</label>
<input className="form-control" type="text" name="name" id="edittournament-textfield-description" value={ description } placeholder={ description } onChange={ this.handleDescriptionInput.bind(this) } />
</div>
<div className="form-group custom-control custom-checkbox">
<input className="custom-control-input" type="checkbox" name="isPublic" id="edittournament-checkbox-isPublic" value={ isPublic } onChange={ this.handlePublicInput.bind(this) } />
<label htmlFor="isPublic" className="custom-control-label">Das Turnier öffentlich anzeigen</label>
</div>
<div className="form-group">
<div className="input-group">
<Button color="success" className="px-5" id="edittournament-button" onClick={ this.handleClick.bind(this) }>Ändern</Button>
</div>
</div>
</div>
);
}
notifyOfContentUpdate() {
const { name, description, isPublic } = this.props;
this.setState({
name : name? name : '',
description : description? description : '',
isPublic : isPublic
});
}
handleClick() {
// TODO: Apply changes to the tournament properties
}
handleNameInput(input) {
this.setState({ name : input.target.value });
}
handleDescriptionInput(input) {
this.setState({ description : input.target.value });
}
handlePublicInput(input) {
this.setState({ public : input.target.value });
}
}
function mapStateToTournamentFormProps(state) {
const { name, description, isPublic } = state.tournamentinfo;
return { name, description, isPublic };
}
const VisibleEditTournamentForm = connect(
mapStateToTournamentFormProps,
null, null, { withRef : true}
)(EditTournamentForm);
class EditTeamField extends React.Component {
render() {
return (
<Card className="container my-4">
<CardBody>
<h2>Team-Namen ändern</h2>
<VisibleEditTeamNamesForm ref={(form) => { this._visibleeditteamnamesform = form; }}/>
</CardBody>
</Card>
);
}
notifyOfContentUpdate() {
this._visibleeditteamnamesform.getWrappedInstance().notifyOfContentUpdate();
}
}
class EditTeamNamesForm extends React.Component {
constructor(props) {
super(props);
this.state = {
teams : []
};
}
render() {
const { teams } = this.state;
return (
<div>
<Table className="table-striped mt-3">
<tbody>
{
teams.map((team, index) =>
<tr key={index}>
<td><Button outline size="sm" className="changeTeamnameButton" id={ 'editteam-button-team_' + team.id } onClick={ this.handleClick.bind(this, index) }>Ändern</Button></td>
<td className="w-100"><input className="form-control" type="text" id={ 'editteam-textfield-team_' + team.id } value={ team.name } placeholder={ team.name } onChange={ this.handleNameInput.bind(this, index) } /></td>
</tr>
)
}
</tbody>
</Table>
</div>
);
}
notifyOfContentUpdate() {
const { teams } = this.props;
this.setState({
teams : teams
});
}
handleNameInput(index, input) {
var team = this.state.teams.slice();
team[index].name = input.target.value;
this.setState({
teams : team
});
}
handleClick(index) {
updateTeamName(this.state.teams[index], () => {
notify.show('Team Name wurde erfolgreich geändert.', 'success', 5000);
}, () => {
notify.show('Team Name konnte nicht geändert werden.', 'warning', 5000);
});
}
}
function mapStateToTeamFormProps(state) {
const { teams } = state.tournamentinfo;
return { teams };
}
const VisibleEditTeamNamesForm = connect(
mapStateToTeamFormProps,
null, null, { withRef : true }
)(EditTeamNamesForm);

View File

@ -1,11 +1,19 @@
import Head from 'next/head'; import Head from 'next/head';
import React from 'react'; import React from 'react';
import {
verifyCredentials
} from '../js/api';
class FullscreenTournamentPage extends React.Component { class FullscreenTournamentPage extends React.Component {
static async getInitialProps({query}) { static async getInitialProps({query}) {
return {query}; return {query};
} }
componentDidMount() {
verifyCredentials();
}
render() { render() {
return ( return (

View File

@ -22,29 +22,39 @@ import 'bootstrap/dist/css/bootstrap.min.css';
import {BigImage, Footer, TurniereNavigation} from '../js/CommonComponents.js'; import {BigImage, Footer, TurniereNavigation} from '../js/CommonComponents.js';
import '../static/everypage.css'; import '../static/everypage.css';
import '../static/css/tournament.css'; import '../static/css/tournament.css';
import { getRequest, getState } from '../js/api';
function Tournament(props) { import {
// TODO: Change href-prop of the anchor tag to contain the tournament code getRequest,
return ( getState,
<div className='pb-5'> verifyCredentials
<Container> } from '../js/api';
<a href={'/t/' + props.tournament.id + '/edit'} className='btn btn-outline-secondary'>Turnier bearbeiten</a>
<p>{props.tournament.description}</p> class TournamentPage extends React.Component {
<ListGroup>
<ListGroupItem> render() {
{props.tournament.isPublic ? 'Das Turnier ist öffentlich.' : 'Das Turnier ist privat.'} const { id, description, isPublic, code, ownerUsername, playoffStages } = this.props.tournament;
</ListGroupItem>
<ListGroupItem>Turnier-Code: <b>{props.tournament.code}</b></ListGroupItem> // TODO: Change href-prop of the anchor tag to contain the tournament code
<ListGroupItem>von <b>{props.tournament.ownerUsername}</b></ListGroupItem> return (
</ListGroup> <div className='pb-5'>
</Container> <Container>
<div className='stages pt-5'> <a href={'/t/' + id + '/edit'} className='btn btn-outline-secondary'>Turnier bearbeiten</a>
{props.tournament.playoffStages.map(stage => <p>{description}</p>
<Stage level={getLevelName(stage.level)} matches={stage.matches} key={stage.level}/>)} <ListGroup>
<ListGroupItem>
{isPublic ? 'Das Turnier ist öffentlich.' : 'Das Turnier ist privat.'}
</ListGroupItem>
<ListGroupItem>Turnier-Code: <b>{code}</b></ListGroupItem>
<ListGroupItem>von <b>{ownerUsername}</b></ListGroupItem>
</ListGroup>
</Container>
<div className='stages pt-5'>
{playoffStages.map(stage =>
<Stage level={getLevelName(stage.level)} matches={stage.matches} key={stage.level}/>)}
</div>
</div> </div>
</div> );
); }
} }
function getLevelName(levelNumber) { function getLevelName(levelNumber) {
@ -57,10 +67,12 @@ function getLevelName(levelNumber) {
} }
function TournamentContainer(props) { function TournamentContainer(props) {
if (props.data === null) { const { tournament } = props.data;
if (tournament === null) {
return <Container>null</Container>; return <Container>null</Container>;
} else { } else {
return <Tournament tournament={props.data.tournament}/>; return <TournamentPage tournament={tournament}/>;
} }
} }
@ -339,9 +351,24 @@ function convertMatch(apiMatch) {
} }
class Main extends React.Component { class Main extends React.Component {
static async getInitialProps({query}) {
return {query};
}
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {
tournament : null
};
}
componentDidMount() {
verifyCredentials();
const code = this.props.query.code; const code = this.props.query.code;
getRequest(getState(), '/tournaments/' + code) getRequest(getState(), '/tournaments/' + code)
.then(response => { .then(response => {
this.setState({tournament: convertTournament(response.data)}); this.setState({tournament: convertTournament(response.data)});
@ -349,12 +376,9 @@ class Main extends React.Component {
.catch(() => { /* TODO: Show some kind of error or smth */ }); .catch(() => { /* TODO: Show some kind of error or smth */ });
} }
static async getInitialProps({query}) {
return {query};
}
render() { render() {
const tournamentName = this.state === null ? 'Turnier' : this.state.tournament.name; const tournamentName = this.state.tournament === null ? 'Turnier' : this.state.tournament.name;
return ( return (
<div> <div>
<Head> <Head>

View File

@ -8,7 +8,7 @@ const handle = app.getRequestHandler();
app.prepare() app.prepare()
.then(() => { .then(() => {
const server = express(); const server = express();
server.get('/t/:code', (req, res) => { server.get('/t/:code', (req, res) => {
const actualPage = '/tournament'; const actualPage = '/tournament';
const queryParam = { code: req.params.code }; const queryParam = { code: req.params.code };
@ -21,6 +21,12 @@ app.prepare()
app.render(req, res, actualPage, queryParam); app.render(req, res, actualPage, queryParam);
}); });
server.get('/t/:code/edit', (req, res) => {
const actualPage = '/tournament-edit';
const queryParam = { code: req.params.code };
app.render(req, res, actualPage, queryParam);
});
server.get('*', (req, res) => { server.get('*', (req, res) => {
return handle(req, res); return handle(req, res);
}); });

View File

@ -1,4 +1,8 @@
.index-cards { .index-cards {
background: url("/static/images/tennis-blurred.jpg") no-repeat center; background: url("/static/images/tennis-blurred.jpg") no-repeat center;
background-size: cover; background-size: cover;
}
h2, h3, h4 {
font-family: Halt, sans-serif;
} }

View File

@ -5635,7 +5635,7 @@ number-is-nan@^1.0.0:
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: object-assign@^4.0.0, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
@ -6388,6 +6388,14 @@ react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4:
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-notify-toast@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/react-notify-toast/-/react-notify-toast-0.5.0.tgz#b00cf50a3cc97a1d222ecd7d7a8e7f14bef5fa67"
integrity sha1-sAz1CjzJeh0iLs19eo5/FL71+mc=
dependencies:
object-assign "^4.0.0"
prop-types "^15.5.8"
react-popper@^0.10.4: react-popper@^0.10.4:
version "0.10.4" version "0.10.4"
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-0.10.4.tgz#af2a415ea22291edd504678d7afda8a6ee3295aa" resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-0.10.4.tgz#af2a415ea22291edd504678d7afda8a6ee3295aa"