Merge pull request #49 from turniere/ticket/TURNIERE-233

Ticket/turniere 233: Add page content loading indicators
This commit is contained in:
betanummeric 2019-10-25 12:30:15 +02:00 committed by GitHub
commit 475e00157c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 102 additions and 24 deletions

View File

@ -2,7 +2,9 @@ stages:
- build
- deploy
kaniko:
kaniko:
tags:
- docker
stage: build
image:
name: gcr.io/kaniko-project/executor:debug
@ -11,7 +13,9 @@ kaniko:
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:latest --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
kubernetes:
kubernetes:
tags:
- turniere-deploy
stage: deploy
only:
- master

View File

@ -344,6 +344,7 @@ const reducerTournamentStatistics = (state = defaultStateTournamentStatistics, a
storeOptionalToken(error.response);
}
action.parameters.errorCallback();
return Object.assign({}, state, {loaded: true});
});
return state;
case actionTypesTournamentStatistics.INT_REQUEST_TOURNAMENT_STATISTICS:
@ -361,11 +362,12 @@ const reducerTournamentStatistics = (state = defaultStateTournamentStatistics, a
storeOptionalToken(error.response);
}
action.parameters.errorCallback();
return Object.assign({}, state, {loaded: true});
});
return Object.assign({}, state, action.parameters.tournamentInfo);
return Object.assign({}, state, action.parameters.tournamentInfo, {loaded: true});
case actionTypesTournamentStatistics.REQUEST_TOURNAMENT_STATISTICS_SUCCESS:
action.parameters.successCallback();
return Object.assign({}, state, action.parameters.tournamentStatistics);
return Object.assign({}, state, action.parameters.tournamentStatistics, {loaded: true});
default: return state;
}
};

View File

@ -0,0 +1,23 @@
import Head from 'next/head';
import {TurniereNavigation} from './Navigation';
import {Container} from 'reactstrap';
import {Footer} from './Footer';
import React from 'react';
export function LoadingPage(props) {
return (<div>
<Head>
<title>{props.title}</title>
</Head>
<TurniereNavigation/>
<Container>
<div className='text-center'>
<img src='/static/images/logo-questionmark.png' alt='' className='loading-logo'/>
</div>
<div className='py-5 text-center w-100 h1 custom-font'>
{props.text}
</div>
</Container>
<Footer/>
</div>);
}

View File

@ -1,35 +1,51 @@
import React from 'react';
import {requestTournamentList} from '../api';
import {Spinner} from 'react-bootstrap';
export default class TournamentList extends React.Component {
constructor(props) {
super(props);
this.state = {
tournaments: []
tournaments: [],
loaded: false
};
}
componentDidMount() {
requestTournamentList(this.props.type, tournaments => {
this.setState({
tournaments: tournaments
tournaments: tournaments,
loaded: true
});
}, () => {});
}, () => {
this.setState({loaded: true});
});
}
render() {
if (this.state.tournaments.length === 0) {
return <p className="text-center border-light font-italic text-secondary border-top border-bottom p-1">keine
Turniere vorhanden</p>;
} else {
return this.state.tournaments.map(item => (
// The code should be item.code but the api just supports it this way by now
<TournamentListEntry name={item.name} code={item.id} key={item.id}/>
));
if (!this.state.loaded) {
return (<EmptyList>
<Spinner animation='border' role='status' size='sm'/>
<span className='ml-3'>lade Turnier-Liste</span>
</EmptyList>);
}
if (this.state.tournaments.length === 0) {
return <EmptyList>keine Turniere vorhanden</EmptyList>;
}
return this.state.tournaments.map(item => (
// The code should be item.code but the api just supports it this way by now
<TournamentListEntry name={item.name} code={item.id} key={item.id}/>
));
}
}
function EmptyList(props) {
return (<p className="text-center border-light font-italic text-secondary border-top border-bottom p-1">
{props.children}
</p>);
}
function TournamentListEntry(props) {
return (
<a className="w-100 d-inline-block mt-2 text-left btn btn-outline-primary" href={'/t/' + props.code}>

View File

@ -16,6 +16,7 @@ export const defaultStateTournamentStatistics = {
owner_username: '',
isPublic: '',
loaded: false,
statistics_available: false,
most_dominant_team: {},

View File

@ -19,6 +19,7 @@ import 'bootstrap/dist/css/bootstrap.min.css';
import '../static/css/everypage.css';
import '../static/css/index.css';
import {LoadingPage} from '../js/components/LoadingPage';
class EditTournamentPage extends React.Component {
static async getInitialProps({query}) {
@ -29,26 +30,31 @@ class EditTournamentPage extends React.Component {
super(props);
this.state = {
validCode: false
validCode: false,
loaded: false
};
}
componentDidMount() {
requestTournament(this.props.query.code, () => {
this.setState({validCode: true});
this.setState({validCode: true, loaded: true});
if (this._edittournamentcontent != null) {
this._edittournamentcontent.notifyOfContentUpdate();
}
}, () => {
this.setState({validCode: false});
this.setState({validCode: false, loaded: true});
});
}
render() {
const {validCode} = this.state;
const {validCode, loaded} = this.state;
const {tournamentname, ownerUsername, isSignedIn, username} = this.props;
if (!loaded) {
return <LoadingPage title='turnie.re' text='Turnier-Einstellungen werden geladen...'/>;
}
return (<UserRestrictor>
<Option condition={validCode && isSignedIn && ownerUsername === username}>
<div className='pb-5'>

View File

@ -11,6 +11,7 @@ import {requestTournamentStatistics} from '../js/api';
import {EditButton, TournamentStatusBar, TournamentStatusBarButton} from '../js/components/TournamentStatusBar';
import Navbar from 'react-bootstrap/Navbar';
import {TournamentBigImage} from '../js/components/TournamentBigImage';
import {LoadingPage} from '../js/components/LoadingPage';
class StatisticsTournamentPage extends React.Component {
static async getInitialProps({query}) {
@ -24,6 +25,10 @@ class StatisticsTournamentPage extends React.Component {
render() {
const {tournamentStatistics} = this.props;
if (!tournamentStatistics.loaded) {
return <LoadingPage title='turnie.re' text='Statistiken zum Turnier werden geladen...'/>;
}
return (
<div>
<Head>

View File

@ -12,11 +12,12 @@ import 'bootstrap/dist/css/bootstrap.min.css';
import '../static/css/everypage.css';
import '../static/css/tournament.css';
import {getTournament} from '../js/redux/tournamentApi';
import {PlayoffStages} from '../js/components/PlayoffStages';
import GroupStage from '../js/components/GroupStage';
import {TournamentBigImage} from '../js/components/TournamentBigImage';
import {EditButton, TournamentStatusBar, TournamentStatusBarButton} from '../js/components/TournamentStatusBar';
import {LoadingPage} from '../js/components/LoadingPage';
import {getTournament} from '../js/redux/tournamentApi';
class PrivateTournamentPage extends React.Component {
render() {
@ -71,7 +72,8 @@ class Main extends React.Component {
super(props);
this.state = {
tournament: null
tournament: null,
loaded: false
};
this.onTournamentRequestSuccess = this.onTournamentRequestSuccess.bind(this);
this.onTournamentRequestError = this.onTournamentRequestError.bind(this);
@ -82,14 +84,14 @@ class Main extends React.Component {
}
onTournamentRequestSuccess(requestStatus, tournament) {
this.setState({status: requestStatus, tournament: tournament});
this.setState({status: requestStatus, tournament: tournament, loaded: true});
}
onTournamentRequestError(error) {
if (error.response) {
this.setState({status: error.response.status});
this.setState({status: error.response.status, loaded: true});
} else {
this.setState({status: -1});
this.setState({status: -1, loaded: true});
}
}
@ -109,6 +111,9 @@ class Main extends React.Component {
<Footer/>
</div>);
} else {
if (!this.state.loaded) {
return <LoadingPage title='turnie.re' text='Turnier wird geladen...'/>;
}
return <ErrorPageComponent code={status}/>;
}
}

View File

@ -72,3 +72,19 @@ footer {
background-size: cover;
min-height: 100vh;
}
.loading-logo {
animation: blink .8s ease-in-out infinite;
}
@keyframes blink {
0% {
filter: grayscale(100%);
}
70% {
filter: grayscale(0%);
}
100% {
filter: grayscale(100%);
}
}