Merge pull request #7 from turniere/ticket/TURNIERE-94

Block sites for unregistered users
This commit is contained in:
Jonny 2019-04-09 08:34:02 +02:00 committed by GitHub
commit ebbff7b01a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 297 additions and 159 deletions

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
/.next/
/node_modules/
/.idea
/.idea
check-syntax.sh

View File

@ -2,20 +2,27 @@ import {
Badge,
Button,
ButtonGroup,
Card,
CardBody,
Container,
Collapse,
Form,
FormGroup,
Nav,
Navbar,
NavbarBrand,
NavbarToggler,
NavItem,
NavLink
NavLink,
Input,
Label
} from 'reactstrap';
import { connect } from 'react-redux';
import React from 'react';
import { logout } from './api';
import { login, logout } from './api';
export function BigImage(props) {
return (
@ -95,7 +102,6 @@ class InvisibleLoginLogoutButtons extends React.Component {
);
}
}
}
const mapStateToLoginLogoutButtonProperties = (state) => {
@ -124,3 +130,153 @@ export function Footer() {
</footer>
);
}
/**
* This component works just like a switch statement, although the conditions of the first items
* are checked first, and the first component with a condition that is true will be shown.
*
* For single conditions and options any kind of component can be taken, while the Option-component
* is dedicated for this job. The only important thing is that this component has to have a condition property.
*
* You should also give a default option with a condition that always evaluates to true.
*
* A quick example would be some content that is only to be shown when the user is logged in:
*
* function SomeRestrictedContent(props) {
* const { isSignedIn } = props;
*
* return (
* <UserRestrictor>
* <Option condition={isSignedIn}>
* < The restricted content >
* </Option>
* <Option condition={true}>
* < Some default content; in this case some kind of login >
* </Option>
* </UserRestrictor>
* );
* }
*
* In the example you'll have to note that the default option is at the bottom of all the options
* since it would always be taken otherwise (the options' conditions are checked from top to bottom)
*/
export class UserRestrictor extends React.Component {
render() {
const { children } = this.props;
for(var i in children) {
var c = children[i];
if(c.props.condition) {
return c;
}
}
return null;
}
}
export function Option(props) {
return props.children;
}
export function Login(props) {
return (
<Container className="py-5">
<Card className="shadow">
<CardBody>
<h1 className="custom-font">Login</h1>
<Hint hint={props.hint}/>
<LoginForm/>
<div className="mt-3">
<a href="/register" className="mr-3">Account anlegen</a>
<a href="/register#account-requirement">Warum ist ein Account nötig?</a>
</div>
</CardBody>
</Card>
</Container>
);
}
class LoginErrorList extends React.Component {
render() {
const { error, errorMessages } = this.props;
if(error) {
return (
<ul>
{ errorMessages.map((message, index) =>
<li key={index}>
<style jsx>{`
li {
color:red;
}
`}</style>
{message}
</li>
) }
</ul>
);
} else {
return null;
}
}
}
const mapStateToErrorMessages = (state) => {
const { errorMessages, error } = state.userinfo;
return { errorMessages, error };
};
const VisibleLoginErrorList = connect(
mapStateToErrorMessages
)(LoginErrorList);
class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
email : '',
password : ''
};
}
render() {
return (
<Form>
<FormGroup>
<Label for="username">E-Mail-Adresse</Label>
<Input type="email" name="username" value={this.state.email} onChange={ this.handleEmailInput.bind(this) } />
</FormGroup>
<FormGroup>
<Label for="password">Passwort</Label>
<Input type="password" name="password" value={this.state.password} onChange={ this.handlePasswordInput.bind(this) } />
</FormGroup>
<Button onClick={login.bind(this, this.state.email, this.state.password)} color="success" size="lg" className="w-100 shadow-sm">Anmelden</Button>
<VisibleLoginErrorList/>
</Form>
);
}
handlePasswordInput(input) {
this.setState({ password : input.target.value });
}
handleEmailInput(input) {
this.setState({ email : input.target.value });
}
}
function Hint(props) {
if(props.hint != null) {
return (
<h3>{ props.hint }</h3>
);
} else {
return null;
}
}

View File

@ -57,6 +57,7 @@ const defaultstate_tournamentinfo = {
description : '',
id : -1,
name : '',
ownerUsername : '',
isPublic : '',
stages: [],
teams : []
@ -272,6 +273,7 @@ const reducer_tournamentinfo = (state = defaultstate_tournamentinfo, action) =>
description : action.parameters.description,
id : action.parameters.id,
name : action.parameters.name,
ownerUsername : action.parameters.owner_username,
isPublic : action.parameters.public,
stages: action.parameters.stages,
teams : action.parameters.teams

View File

@ -26,7 +26,7 @@ export class ErrorPageComponent extends React.Component {
}
}
function ErrorPage(props){
export function ErrorPage(props){
return (
<Container className="mb-5">
<div className="row mb-5">

View File

@ -1,7 +1,8 @@
import Head from 'next/head';
import '../static/everypage.css';
import { Footer, TurniereNavigation } from '../js/CommonComponents';
import { Footer, TurniereNavigation, UserRestrictor, Option, Login } from '../js/CommonComponents';
import React from 'react';
import { connect } from 'react-redux';
import {
Button,
@ -22,28 +23,56 @@ import {
import EditableStringList from '../js/EditableStringList';
export default class CreatePage extends React.Component {
class PrivateCreatePage extends React.Component {
componentDidMount() {
verifyCredentials();
}
render() {
const { isSignedIn } = this.props;
return (
<div className="main generic-fullpage-bg">
<Head>
<title>Turnier erstellen: turnie.re</title>
</Head>
<TurniereNavigation/>
<div>
<CreateTournamentCard/>
</div>
<Footer/>
</div>
<UserRestrictor>
<Option condition={isSignedIn}>
<div className="main generic-fullpage-bg">
<Head>
<title>Turnier erstellen: turnie.re</title>
</Head>
<TurniereNavigation/>
<div>
<CreateTournamentCard/>
</div>
<Footer/>
</div>
</Option>
<Option condition={true}>
<div className="main generic-fullpage-bg">
<Head>
<title>Anmeldung</title>
</Head>
<TurniereNavigation/>
<div>
<Login hint="Sie müssen angemeldet sein, um diesen Inhalt anzuzeigen!"/>
</div>
<Footer/>
</div>
</Option>
</UserRestrictor>
);
}
}
}
function mapStateToCreatePageProperties(state) {
const { isSignedIn } = state.userinfo;
return { isSignedIn };
}
const CreatePage = connect(
mapStateToCreatePageProperties
)(PrivateCreatePage);
export default CreatePage;
function CreateTournamentCard() {
return (

View File

@ -1,10 +1,7 @@
import Head from 'next/head';
import '../static/everypage.css';
import {Footer, TurniereNavigation} from '../js/CommonComponents';
import { Footer, TurniereNavigation, Login } from '../js/CommonComponents';
import React from 'react';
import { Button, Card, CardBody, Container, Form, FormGroup, Input, Label } from 'reactstrap';
import { login } from '../js/api';
import { connect } from 'react-redux';
import {
verifyCredentials
@ -31,92 +28,3 @@ export default class LoginPage extends React.Component {
);
}
}
function Login() {
return (
<Container className="py-5">
<Card className="shadow">
<CardBody>
<h1 className="custom-font">Login</h1>
<LoginForm/>
<div className="mt-3">
<a href="/register" className="mr-3">Account anlegen</a>
<a href="/register#account-requirement">Warum ist ein Account nötig?</a>
</div>
</CardBody>
</Card>
</Container>
);
}
class LoginErrorList extends React.Component {
render() {
const { error, errorMessages } = this.props;
if(error) {
return (
<ul>
{ errorMessages.map((message, index) =>
<li key={index}>
<style jsx>{`
li {
color:red;
}
`}</style>
{message}
</li>
) }
</ul>
);
} else {
return null;
}
}
}
const mapStateToErrorMessages = (state) => {
const { errorMessages, error } = state.userinfo;
return { errorMessages, error };
};
const VisibleLoginErrorList = connect(
mapStateToErrorMessages
)(LoginErrorList);
class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
email : '',
password : ''
};
}
render() {
return (
<Form>
<FormGroup>
<Label for="username">E-Mail-Adresse</Label>
<Input type="email" name="username" value={this.state.email} onChange={ this.handleEmailInput.bind(this) } />
</FormGroup>
<FormGroup>
<Label for="password">Passwort</Label>
<Input type="password" name="password" value={this.state.password} onChange={ this.handlePasswordInput.bind(this) } />
</FormGroup>
<Button onClick={login.bind(this, this.state.email, this.state.password)} color="success" size="lg" className="w-100 shadow-sm">Anmelden</Button>
<VisibleLoginErrorList/>
</Form>
);
}
handlePasswordInput(input) {
this.setState({ password : input.target.value });
}
handleEmailInput(input) {
this.setState({ email : input.target.value });
}
}

View File

@ -5,7 +5,7 @@ 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 { BigImage, Footer, TurniereNavigation, Login, UserRestrictor, Option } from '../js/CommonComponents.js';
import { ErrorPageComponent } from '../js/components/ErrorComponents.js';
import {
@ -34,7 +34,7 @@ class EditTournamentPage extends React.Component {
super(props);
this.state = {
validCode: true
validCode: false
};
}
@ -42,7 +42,10 @@ class EditTournamentPage extends React.Component {
verifyCredentials();
requestTournament(this.props.query.code, () => {
this.setState({ validCode: true });
this._edittournamentcontent.notifyOfContentUpdate();
if(this._edittournamentcontent != null) {
this._edittournamentcontent.notifyOfContentUpdate();
}
}, () => {
this.setState({ validCode: false });
});
@ -50,33 +53,50 @@ class EditTournamentPage extends React.Component {
render() {
const { validCode } = this.state;
const { name } = this.props;
const { tournamentname, ownerUsername, isSignedIn, username } = this.props;
if(validCode) {
return (
<div className='pb-5'>
<Head>
<title>Turnie.re - Turnier bearbeiten</title>
</Head>
<TurniereNavigation/>
<BigImage text={ name }/>
return (
<UserRestrictor>
<Option condition={ validCode && isSignedIn && ownerUsername === username }>
<div className='pb-5'>
<Head>
<title>Turnie.re - Turnier bearbeiten</title>
</Head>
<TurniereNavigation/>
<EditTournamentContent ref={(edittournamentcontent) => { this._edittournamentcontent = edittournamentcontent; }}/>
<Footer/>
</div>
);
} else {
return (
<ErrorPageComponent statusCode={ 404 }/>
);
}
<BigImage text={ tournamentname }/>
<EditTournamentContent ref={(edittournamentcontent) => { this._edittournamentcontent = edittournamentcontent; }}/>
<Footer/>
</div>
</Option>
<Option condition={ validCode && isSignedIn }>
<ErrorPageComponent statusCode={ 403 }/>
</Option>
<Option condition={ !isSignedIn }>
<div className="main generic-fullpage-bg">
<Head>
<title>Turnie.re - Turnier bearbeiten</title>
</Head>
<TurniereNavigation/>
<div>
<Login hint="Sie müssen angemeldet sein, um ein Turnier zu bearbeiten."/>
</div>
<Footer/>
</div>
</Option>
<Option condition={true}>
<ErrorPageComponent statusCode={ 404 }/>
</Option>
</UserRestrictor>
);
}
}
function mapStateToTournamentInfo(state) {
const { name } = state.tournamentinfo;
return { name };
const { tournamentname, ownerUsername } = state.tournamentinfo;
const { isSignedIn, username } = state.userinfo;
return { tournamentname, ownerUsername, isSignedIn, username };
}
export default connect(

View File

@ -18,10 +18,12 @@ import {
Row,
Table
} from 'reactstrap';
import { ErrorPageComponent } from '../js/components/ErrorComponents.js';
import 'bootstrap/dist/css/bootstrap.min.css';
import {BigImage, Footer, TurniereNavigation} from '../js/CommonComponents.js';
import '../static/everypage.css';
import '../static/css/tournament.css';
import { connect } from 'react-redux';
import {
getRequest,
@ -38,7 +40,7 @@ class TournamentPage extends React.Component {
return (
<div className='pb-5'>
<Container>
<a href={'/t/' + id + '/edit'} className='btn btn-outline-secondary'>Turnier bearbeiten</a>
<EditButton id={id} ownerName={ownerUsername}/>
<p>{description}</p>
<ListGroup>
<ListGroupItem>
@ -57,6 +59,27 @@ class TournamentPage extends React.Component {
}
}
function PrivateEditButton(props) {
const { id, ownerName, isSignedIn, username } = props;
if(isSignedIn && ownerName === username) {
return (
<a href={'/t/' + id + '/edit'} className='btn btn-outline-secondary'>Turnier bearbeiten</a>
);
} else {
return null;
}
}
function mapStateToEditButtonProperties(state) {
const { isSignedIn, username } = state.userinfo;
return { isSignedIn, username };
}
const EditButton = connect(
mapStateToEditButtonProperties
)(PrivateEditButton);
function getLevelName(levelNumber) {
const names = ['Finale', 'Halbfinale', 'Viertelfinale', 'Achtelfinale'];
if(levelNumber < names.length){
@ -66,16 +89,6 @@ function getLevelName(levelNumber) {
}
}
function TournamentContainer(props) {
const { tournament } = props.data;
if (tournament === null) {
return <Container>null</Container>;
} else {
return <TournamentPage tournament={tournament}/>;
}
}
function Stage(props) {
return (<div>
<Container className='py-5'>
@ -371,25 +384,34 @@ class Main extends React.Component {
getRequest(getState(), '/tournaments/' + code)
.then(response => {
this.setState({tournament: convertTournament(response.data)});
this.setState({ status : response.status, tournament : convertTournament(response.data)});
})
.catch(() => { /* TODO: Show some kind of error or smth */ });
.catch((err) => {
this.setState({ status : err.response.status });
});
}
render() {
const tournamentName = this.state.tournament === null ? 'Turnier' : this.state.tournament.name;
return (
<div>
<Head>
<title>{tournamentName}: turnie.re</title>
</Head>
<TurniereNavigation/>
<BigImage text={tournamentName}/>
<TournamentContainer data={this.state}/>
<Footer/>
</div>
);
const { status, tournament } = this.state;
if (status == 200) {
return (
<div>
<Head>
<title>{tournamentName}: turnie.re</title>
</Head>
<TurniereNavigation/>
<BigImage text={tournamentName}/>
<TournamentPage tournament={tournament}/>
<Footer/>
</div>
);
} else {
return <ErrorPageComponent code={status}/>;
}
}
}