Add api calls with persistent user data
Added login and registration api calls to the function pool, while the application state is persistet through localStorage. Also (if signed in) the username will be displayed in the navigation bar of the website.
This commit is contained in:
parent
f19b2d399c
commit
91b532b6cd
|
|
@ -10,6 +10,9 @@ import {
|
|||
NavItem,
|
||||
NavLink
|
||||
} from 'reactstrap';
|
||||
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import React from "react";
|
||||
|
||||
export function BigImage(props) {
|
||||
|
|
@ -26,6 +29,7 @@ export class TurniereNavigation extends React.Component {
|
|||
super(props);
|
||||
|
||||
this.toggle = this.toggle.bind(this);
|
||||
|
||||
this.state = {
|
||||
collapsed: true
|
||||
};
|
||||
|
|
@ -68,15 +72,48 @@ function Betabadge() {
|
|||
return (<Badge color="danger" className="mr-2">BETA</Badge>);
|
||||
}
|
||||
|
||||
function LoginLogoutButtons() {
|
||||
return (
|
||||
<ButtonGroup className="nav-item">
|
||||
<Button href="/login" className="btn navbar-btn btn-outline-success my-2 my-sm-0 px-5">Login</Button>
|
||||
<Button href="/register" className="btn navbar-btn btn-outline-success my-2 my-sm-0 px-5">Registrieren</Button>
|
||||
</ButtonGroup>
|
||||
);
|
||||
class InvisibleLoginLogoutButtons extends React.Component {
|
||||
|
||||
logoutClick() {
|
||||
console.log("Logged out.");
|
||||
|
||||
/*
|
||||
/users/sign_out <- DELETE Token invalidieren
|
||||
/users/validate_token <- GET Token valide?
|
||||
*/
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isSignedIn, username, logout } = this.props
|
||||
|
||||
if(isSignedIn) {
|
||||
return (
|
||||
<ButtonGroup className="nav-item">
|
||||
<Button href="/profile" className="btn navbar-btn btn-outline-success my-2 my-sm-0 px-5">{ username }</Button>
|
||||
<Button onClick={this.logoutClick} className="btn navbar-btn btn-outline-success my-2 my-sm-0 px-5">Logout</Button>
|
||||
</ButtonGroup>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<ButtonGroup className="nav-item">
|
||||
<Button href="/login" className="btn navbar-btn btn-outline-success my-2 my-sm-0 px-5">Login</Button>
|
||||
<Button href="/register" className="btn navbar-btn btn-outline-success my-2 my-sm-0 px-5">Registrieren</Button>
|
||||
</ButtonGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToLoginLogoutButtonProperties = (state) => {
|
||||
const { isSignedIn, username } = state.userinfo;
|
||||
return { isSignedIn, username }
|
||||
}
|
||||
|
||||
const LoginLogoutButtons = connect(
|
||||
mapStateToLoginLogoutButtonProperties
|
||||
)(InvisibleLoginLogoutButtons)
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="footer mt-5 bg-dark text-light">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,203 @@
|
|||
import { createStore, applyMiddleware, combineReducers } from 'redux'
|
||||
import { composeWithDevTools } from 'redux-devtools-extension'
|
||||
import thunkMiddleware from 'redux-thunk'
|
||||
import { errorMessages } from './constants'
|
||||
|
||||
const axios = require('axios');
|
||||
|
||||
const api_url = 'https://api.turnie.re';
|
||||
|
||||
|
||||
const actiontypes_userinfo = {
|
||||
'REGISTER' : 'REGISTER',
|
||||
'REGISTER_RESULT_SUCCESS' : 'REGISTER_RESULT_SUCCESS',
|
||||
'REGISTER_RESULT_ERROR' : 'REGISTER_RESULT_ERROR',
|
||||
|
||||
'LOGIN' : 'LOGIN',
|
||||
'LOGIN_RESULT_SUCCESS' : 'LOGIN_RESULT_SUCCESS',
|
||||
'LOGIN_RESULT_ERROR' : 'LOGIN_RESULT_ERROR',
|
||||
|
||||
'REHYDRATE' : 'USERINFO_REHYDRATE',
|
||||
}
|
||||
|
||||
const defaultstate_userinfo = {
|
||||
isSignedIn : false,
|
||||
username : null,
|
||||
error : false,
|
||||
errorMessages : [],
|
||||
|
||||
accesstoken : null,
|
||||
client : null,
|
||||
expiry : null,
|
||||
uid : null
|
||||
}
|
||||
|
||||
const reducer_userinfo = (state = defaultstate_userinfo, action) => {
|
||||
switch(action.type) {
|
||||
case actiontypes_userinfo.REGISTER:
|
||||
axios.post(api_url + '/users', {
|
||||
'username' : action.parameters.username,
|
||||
'email' : action.parameters.email,
|
||||
'password' : action.parameters.password
|
||||
}).then((resp) => {
|
||||
__store.dispatch({
|
||||
type : actiontypes_userinfo.REGISTER_RESULT_SUCCESS
|
||||
})
|
||||
}).catch((error) => {
|
||||
if (error.response) {
|
||||
__store.dispatch({
|
||||
'type' : actiontypes_userinfo.REGISTER_RESULT_ERROR,
|
||||
'parameters' : {
|
||||
'errorMessages' : error.response.data.errors.full_messages
|
||||
}
|
||||
})
|
||||
} else {
|
||||
__store.dispatch({
|
||||
'type' : actiontypes_userinfo.REGISTER_RESULT_ERROR,
|
||||
'parameters' : {
|
||||
'errorMessages' : [
|
||||
errorMessages['registration_errorunknown']['en']
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
return Object.assign({}, state, {});
|
||||
case actiontypes_userinfo.REGISTER_RESULT_SUCCESS:
|
||||
return Object.assign({}, state, {
|
||||
error : false,
|
||||
errorMessages : []
|
||||
});
|
||||
case actiontypes_userinfo.REGISTER_RESULT_ERROR:
|
||||
return Object.assign({}, state, {
|
||||
error : true,
|
||||
errorMessages : action.parameters.errorMessages
|
||||
});
|
||||
case actiontypes_userinfo.LOGIN:
|
||||
axios.post(api_url + '/users/sign_in', {
|
||||
email : action.parameters.email,
|
||||
password : action.parameters.password
|
||||
}).then((resp) => {
|
||||
console.log(resp);
|
||||
__store.dispatch({
|
||||
type : actiontypes_userinfo.LOGIN_RESULT_SUCCESS,
|
||||
parameters : {
|
||||
username : resp.data.data.username,
|
||||
accesstoken : resp.headers['access-token'],
|
||||
client : resp.headers['client'],
|
||||
expiry : resp.headers['expiry'],
|
||||
uid : resp.headers['uid']
|
||||
}
|
||||
});
|
||||
}).catch((error) => {
|
||||
if(error.response) {
|
||||
console.log(error.response);
|
||||
__store.dispatch({
|
||||
'type' : actiontypes_userinfo.LOGIN_RESULT_ERROR,
|
||||
'parameters' : {
|
||||
'errorMessages' : error.response.data.errors
|
||||
}
|
||||
})
|
||||
} else {
|
||||
console.log(error);
|
||||
console.log(errorMessages['login_errorunknown']['en']);
|
||||
__store.dispatch({
|
||||
type : actiontypes_userinfo.LOGIN_RESULT_ERROR,
|
||||
parameters : [
|
||||
errorMessages['login_errorunknown']['en']
|
||||
]
|
||||
});
|
||||
}
|
||||
});
|
||||
return Object.assign({}, state, {});
|
||||
case actiontypes_userinfo.LOGIN_RESULT_SUCCESS:
|
||||
return Object.assign({}, state, {
|
||||
isSignedIn : true,
|
||||
error : false,
|
||||
errorMessages : [],
|
||||
|
||||
username : action.parameters.username,
|
||||
|
||||
accesstoken : action.parameters.accesstoken,
|
||||
client : action.parameters.client,
|
||||
expiry : action.parameters.expiry,
|
||||
uid : action.parameters.uid
|
||||
});
|
||||
case actiontypes_userinfo.LOGIN_RESULT_ERROR:
|
||||
return Object.assign({}, state, {
|
||||
error : true,
|
||||
errorMessages : action.parameters.errorMessages
|
||||
});
|
||||
case actiontypes_userinfo.REHYDRATE:
|
||||
return Object.assign({}, state, action.parameters);
|
||||
default: return state;
|
||||
}
|
||||
}
|
||||
|
||||
const reducers = {
|
||||
userinfo: reducer_userinfo
|
||||
}
|
||||
|
||||
const default_applicationstate = {
|
||||
userinfo : defaultstate_userinfo
|
||||
}
|
||||
|
||||
var __store;
|
||||
|
||||
export function initializeStore(initialState = default_applicationstate) {
|
||||
__store = createStore(
|
||||
combineReducers(reducers),
|
||||
initialState,
|
||||
composeWithDevTools(applyMiddleware(thunkMiddleware))
|
||||
);
|
||||
__store.subscribe(() => {
|
||||
localStorage.setItem('reduxState', JSON.stringify(__store.getState()))
|
||||
});
|
||||
return __store;
|
||||
}
|
||||
|
||||
export function verifyCredentials() {
|
||||
rehydrateApplicationState();
|
||||
}
|
||||
|
||||
export function register(username, email, password) {
|
||||
__store.dispatch({
|
||||
type: actiontypes_userinfo.REGISTER,
|
||||
parameters: {
|
||||
username: username,
|
||||
email: email,
|
||||
password: password
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function login(email, password) {
|
||||
__store.dispatch({
|
||||
type: actiontypes_userinfo.LOGIN,
|
||||
parameters: {
|
||||
email: email,
|
||||
password: password
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function getState() {
|
||||
return __store.getState();
|
||||
}
|
||||
|
||||
function rehydrateApplicationState() {
|
||||
const persistedState = localStorage.getItem('reduxState') ?
|
||||
JSON.parse(localStorage.getItem('reduxState')) :
|
||||
undefined;
|
||||
|
||||
if(persistedState) {
|
||||
__store.dispatch({
|
||||
type : actiontypes_userinfo.REHYDRATE,
|
||||
parameters : Object.assign({}, persistedState.userinfo, {})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import React from "react";
|
||||
|
||||
export function BigImage(props) {
|
||||
return (
|
||||
<div className="big-image">
|
||||
<h1 className="display-1">{props.text}</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
|
||||
export const errorMessages = {
|
||||
registration_errorunknown : {
|
||||
en : 'An unknown error prevented a successful registration.'
|
||||
},
|
||||
login_errorunknown : {
|
||||
en : 'An unknown error prevented a successful login.'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import React from 'react'
|
||||
import { initializeStore } from '../api'
|
||||
|
||||
const isServer = typeof window === 'undefined'
|
||||
const __NEXT_REDUX_STORE__ = '__NEXT_REDUX_STORE__'
|
||||
|
||||
function getOrCreateStore (initialState) {
|
||||
// Always make a new store if server, otherwise state is shared between requests
|
||||
if (isServer) {
|
||||
return initializeStore(initialState)
|
||||
}
|
||||
|
||||
// Create store if unavailable on the client and set it on the window object
|
||||
if (!window[__NEXT_REDUX_STORE__]) {
|
||||
window[__NEXT_REDUX_STORE__] = initializeStore(initialState)
|
||||
}
|
||||
return window[__NEXT_REDUX_STORE__]
|
||||
}
|
||||
|
||||
export default (App) => {
|
||||
return class AppWithRedux extends React.Component {
|
||||
static async getInitialProps (appContext) {
|
||||
// Get or Create the store with `undefined` as initialState
|
||||
// This allows you to set a custom default initialState
|
||||
const reduxStore = getOrCreateStore()
|
||||
|
||||
// Provide the store to getInitialProps of pages
|
||||
appContext.ctx.reduxStore = reduxStore
|
||||
|
||||
let appProps = {}
|
||||
if (typeof App.getInitialProps === 'function') {
|
||||
appProps = await App.getInitialProps(appContext)
|
||||
}
|
||||
|
||||
return {
|
||||
...appProps,
|
||||
initialReduxState: reduxStore.getState()
|
||||
}
|
||||
}
|
||||
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.reduxStore = getOrCreateStore(props.initialReduxState)
|
||||
}
|
||||
|
||||
render () {
|
||||
return <App {...this.props} reduxStore={this.reduxStore} />
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,11 @@
|
|||
"next": "^7.0.2",
|
||||
"react": "^16.6.1",
|
||||
"react-dom": "^16.6.1",
|
||||
"reactstrap": "^6.5.0"
|
||||
"react-redux": "^5.1.1",
|
||||
"reactstrap": "^6.5.0",
|
||||
"redux": "^4.0.1",
|
||||
"redux-devtools-extension": "^2.13.7",
|
||||
"redux-thunk": "^2.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"react-editable-list": "0.0.3"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
import App, {Container} from 'next/app'
|
||||
import React from 'react'
|
||||
import { Provider } from 'react-redux'
|
||||
import withReduxStore from '../js/redux/reduxStoreBinder'
|
||||
import { verifyCredentials } from '../js/api'
|
||||
|
||||
class TurniereApp extends App {
|
||||
|
||||
componentDidMount() {
|
||||
verifyCredentials();
|
||||
}
|
||||
|
||||
render () {
|
||||
const {Component, pageProps, reduxStore} = this.props
|
||||
return (
|
||||
<Container>
|
||||
<Provider store={reduxStore}>
|
||||
<Component {...pageProps} />
|
||||
</Provider>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default withReduxStore(TurniereApp)
|
||||
|
|
@ -5,11 +5,13 @@ import {Alert, Button, Card, CardBody} from 'reactstrap';
|
|||
|
||||
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/css/index.css'
|
||||
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
function Main() {
|
||||
return (
|
||||
<div className="main">
|
||||
|
|
@ -164,14 +166,21 @@ function PromotedLinkCreateTournament() {
|
|||
</Card>);
|
||||
}
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Head>
|
||||
<title>turnie.re</title>
|
||||
</Head>
|
||||
<TurniereNavigation/>
|
||||
<BigImage text="Einfach Turniere organisieren"/>
|
||||
<Main/>
|
||||
<Footer/>
|
||||
</div>
|
||||
);
|
||||
|
||||
class Index extends React.Component {
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<Head>
|
||||
<title>turnie.re</title>
|
||||
</Head>
|
||||
<TurniereNavigation/>
|
||||
<BigImage text="Einfach Turniere organisieren"/>
|
||||
<Main/>
|
||||
<Footer/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(Index);
|
||||
|
|
|
|||
Loading…
Reference in New Issue