import {applyMiddleware, combineReducers, createStore} from 'redux'; import {composeWithDevTools} from 'redux-devtools-extension'; import thunkMiddleware from 'redux-thunk'; import {errorMessages} from './constants'; import {actionTypesUserinfo, defaultStateUserinfo} from './redux/userInfo'; import {actionTypesTournamentinfo, defaultStateTournamentinfo} from './redux/tournamentInfo'; import { actionTypesTournamentStatistics, defaultStateTournamentStatistics, transformTournamentInfoToStatistics, transformTournamentStatsToStatistics } from './redux/tournamentStatistics'; import {actionTypesTournamentlist, defaultStateTournamentlist} from './redux/tournamentList'; import {deleteRequest, getRequest, patchRequest, postRequest} from './redux/backendApi'; function storeOptionalToken(response) { if (checkForAuthenticationHeaders(response)) { __store.dispatch({ type: actionTypesUserinfo.STORE_AUTH_HEADERS, parameters: { accesstoken: response.headers['access-token'], client: response.headers['client'], expiry: response.headers['expiry'], uid: response.headers['uid'] } }); } } function checkForAuthenticationHeaders(response) { if (response.headers) { const requiredHeaders = [ 'access-token', 'client', 'uid', 'expiry' ]; for (let i = 0; i < requiredHeaders.length; i++) { if (!response.headers[requiredHeaders[i]]) { return false; } } return true; } return false; } const reducerUserinfo = (state = defaultStateUserinfo, action) => { switch (action.type) { case actionTypesUserinfo.REGISTER: postRequest(action.state, '/users', { 'username': action.parameters.username, 'email': action.parameters.email, 'password': action.parameters.password }).then(resp => { __store.dispatch({ type: actionTypesUserinfo.REGISTER_RESULT_SUCCESS }); storeOptionalToken(resp); }).catch(error => { if (error.response) { __store.dispatch({ 'type': actionTypesUserinfo.REGISTER_RESULT_ERROR, 'parameters': { 'errorMessages': error.response.data.errors.full_messages } }); storeOptionalToken(error.response); } else { __store.dispatch({ 'type': actionTypesUserinfo.REGISTER_RESULT_ERROR, 'parameters': { 'errorMessages': [ errorMessages['registration_errorunknown']['en'] ] } }); } }); return Object.assign({}, state, {}); case actionTypesUserinfo.REGISTER_RESULT_SUCCESS: return Object.assign({}, state, { error: false, errorMessages: [] }); case actionTypesUserinfo.REGISTER_RESULT_ERROR: return Object.assign({}, state, { error: true, errorMessages: action.parameters.errorMessages }); case actionTypesUserinfo.LOGIN: postRequest(action.state, '/users/sign_in', { email: action.parameters.email, password: action.parameters.password }).then(resp => { __store.dispatch({ type: actionTypesUserinfo.LOGIN_RESULT_SUCCESS, parameters: { username: resp.data.username, successCallback: action.parameters.successCallback } }); storeOptionalToken(resp); }).catch(error => { if (error.response) { __store.dispatch({ 'type': actionTypesUserinfo.LOGIN_RESULT_ERROR, 'parameters': { 'errorMessages': error.response.data.errors } }); storeOptionalToken(error.response); } else { __store.dispatch({ 'type': actionTypesUserinfo.LOGIN_RESULT_ERROR, 'parameters': { 'errorMessages': [errorMessages['login_errorunknown']['en']] } }); } }); return Object.assign({}, state, {}); case actionTypesUserinfo.LOGIN_RESULT_SUCCESS: action.parameters.successCallback(action.parameters.username); return Object.assign({}, state, { isSignedIn: true, error: false, errorMessages: [], username: action.parameters.username }); case actionTypesUserinfo.LOGIN_RESULT_ERROR: return Object.assign({}, state, { error: true, errorMessages: action.parameters.errorMessages }); case actionTypesUserinfo.LOGOUT: deleteRequest(action.state, '/users/sign_out').then(() => { action.parameters.successCallback(); __store.dispatch({type: actionTypesUserinfo.CLEAR}); }).catch(() => { __store.dispatch({type: actionTypesUserinfo.CLEAR}); }); return Object.assign({}, state, {}); case actionTypesUserinfo.STORE_AUTH_HEADERS: return Object.assign({}, state, { accesstoken: action.parameters.accesstoken, client: action.parameters.client, expiry: action.parameters.expiry, uid: action.parameters.uid }); case actionTypesUserinfo.VERIFY_CREDENTIALS: getRequest(action.state, '/users/validate_token').then(resp => { storeOptionalToken(resp); }).catch(() => { __store.dispatch({type: actionTypesUserinfo.CLEAR}); }); return Object.assign({}, state, {}); case actionTypesUserinfo.REHYDRATE: return Object.assign({}, state, action.parameters, {error: false, errorMessages: []}); case actionTypesUserinfo.CLEAR: return Object.assign({}, state, { isSignedIn: false, username: null, error: false, errorMessages: [], accesstoken: null, client: null, expiry: null, uid: null }); default: return state; } }; const reducerTournamentinfo = (state = defaultStateTournamentinfo, action) => { switch (action.type) { case actionTypesTournamentinfo.CREATE_TOURNAMENT: postRequest(action.state, '/tournaments', action.parameters.tournament).then(resp => { storeOptionalToken(resp); action.parameters.successCallback(); }).catch(() => { action.parameters.errorCallback(); }); return Object.assign({}, state, {}); case actionTypesTournamentinfo.REQUEST_TOURNAMENT: getRequest(action.state, '/tournaments/' + action.parameters.code).then(resp => { __store.dispatch({ type: actionTypesTournamentinfo.REQUEST_TOURNAMENT_SUCCESS, parameters: resp.data }); storeOptionalToken(resp); action.parameters.successCallback(); }).catch(() => { action.parameters.errorCallback(); }); return Object.assign({}, state, {}); case actionTypesTournamentinfo.REQUEST_TOURNAMENT_SUCCESS: return Object.assign({}, state, { code: action.parameters.code, 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 }); case actionTypesTournamentinfo.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 actionTypesTournamentinfo.MODIFY_TOURNAMENT_SUCCESS: return Object.assign({}, state, {}); case actionTypesTournamentinfo.MODIFY_TOURNAMENT_ERROR: return Object.assign({}, state, {}); case actionTypesTournamentinfo.REHYDRATE: return Object.assign({}, state, {}); case actionTypesTournamentinfo.START_MATCH: patchRequest(action.state, '/matches/' + action.parameters.matchId, { state: 'in_progress' }).then(resp => { storeOptionalToken(resp); action.parameters.successCallback(); }).catch(error => { if (error.response) { storeOptionalToken(error.response); } action.parameters.errorCallback(); }); return Object.assign({}, state, {}); case actionTypesTournamentinfo.END_MATCH: patchRequest(action.state, '/matches/' + action.parameters.matchId, { state: 'finished' }).then(resp => { storeOptionalToken(resp); action.parameters.successCallback(resp.data.winner); }).catch(error => { if (error.response) { storeOptionalToken(error.response); } action.parameters.errorCallback(); }); return Object.assign({}, state, {}); case actionTypesTournamentinfo.CLEAR: return Object.assign({}, state, {}); default: return state; } }; const reducerTournamentStatistics = (state = defaultStateTournamentStatistics, action) => { switch (action.type) { case actionTypesTournamentStatistics.REQUEST_TOURNAMENT_STATISTICS: getRequest(action.state, '/tournaments/' + action.parameters.code).then(resp => { storeOptionalToken(resp); __store.dispatch({ type: actionTypesTournamentStatistics.INT_REQUEST_TOURNAMENT_STATISTICS, state: action.state, parameters: { code: action.parameters.code, tournamentInfo: transformTournamentInfoToStatistics(resp.data), successCallback: action.parameters.successCallback, errorCallback: action.parameters.errorCallback } }); }).catch(error => { if (error.response) { storeOptionalToken(error.response); } action.parameters.errorCallback(); }); return state; case actionTypesTournamentStatistics.INT_REQUEST_TOURNAMENT_STATISTICS: getRequest(action.state, '/tournaments/' + action.parameters.code + '/statistics').then(resp => { storeOptionalToken(resp); __store.dispatch({ type: actionTypesTournamentStatistics.REQUEST_TOURNAMENT_STATISTICS_SUCCESS, parameters: { tournamentStatistics: transformTournamentStatsToStatistics(resp.data), successCallback: action.parameters.successCallback } }); }).catch(error => { if (error.response) { storeOptionalToken(error.response); } action.parameters.errorCallback(); }); return Object.assign({}, state, action.parameters.tournamentInfo); case actionTypesTournamentStatistics.REQUEST_TOURNAMENT_STATISTICS_SUCCESS: action.parameters.successCallback(); return Object.assign({}, state, action.parameters.tournamentStatistics); default: return state; } }; const reducerTournamentlist = (state = defaultStateTournamentlist, action) => { switch (action.type) { case actionTypesTournamentlist.FETCH: getRequest(action.state, '/tournaments?type=' + action.parameters.type).then(resp => { __store.dispatch({ type: actionTypesTournamentlist.FETCH_SUCCESS, parameters: resp.data }); storeOptionalToken(resp); action.parameters.successCallback(resp.data); }).catch(error => { if (error.response) { storeOptionalToken(error.response); } action.parameters.errorCallback(); }); return state; case actionTypesTournamentlist.FETCH_SUCCESS: return Object.assign({}, state, {tournaments: action.parameters}); default: return state; } }; const reducers = { userinfo: reducerUserinfo, tournamentinfo: reducerTournamentinfo, tournamentStatistics: reducerTournamentStatistics, tournamentlist: reducerTournamentlist }; const defaultApplicationState = { userinfo: defaultStateUserinfo, tournamentinfo: defaultStateTournamentinfo, tournamentStatistics: defaultStateTournamentStatistics, tournamentlist: defaultStateTournamentlist }; let __store; let applicationHydrated = false; export function initializeStore(initialState = defaultApplicationState) { __store = createStore( combineReducers(reducers), initialState, composeWithDevTools(applyMiddleware(thunkMiddleware)) ); __store.subscribe(() => { if (applicationHydrated) { localStorage.setItem('reduxState', JSON.stringify(__store.getState())); } }); return __store; } export function verifyCredentials() { rehydrateApplicationState(); if (__store.getState().userinfo.isSignedIn) { __store.dispatch({ type: actionTypesUserinfo.VERIFY_CREDENTIALS, state: __store.getState() }); } } export function register(username, email, password) { __store.dispatch({ type: actionTypesUserinfo.REGISTER, parameters: { username: username, email: email, password: password }, state: __store.getState() }); } export function login(email, password, successCallback) { __store.dispatch({ type: actionTypesUserinfo.LOGIN, parameters: { email: email, password: password, successCallback: successCallback }, state: __store.getState() }); } export function logout(successCallback) { __store.dispatch({ type: actionTypesUserinfo.LOGOUT, parameters: { successCallback: successCallback }, state: __store.getState() }); } export function createTournament(data, successCallback, errorCallback) { __store.dispatch({ type: actionTypesTournamentinfo.CREATE_TOURNAMENT, parameters: { tournament: data, successCallback: successCallback, errorCallback: errorCallback }, state: __store.getState() }); } export function requestTournament(code, successCallback, errorCallback) { __store.dispatch({ type: actionTypesTournamentinfo.REQUEST_TOURNAMENT, parameters: { code: code, successCallback: successCallback, errorCallback: errorCallback }, state: __store.getState() }); } export function requestTournamentStatistics(code, successCallback, errorCallback) { __store.dispatch({ type: actionTypesTournamentStatistics.REQUEST_TOURNAMENT_STATISTICS, parameters: { code: code, successCallback: successCallback, errorCallback: errorCallback }, state: __store.getState() }); } export function updateTeamName(team, successCB, errorCB) { __store.dispatch({ type: actionTypesTournamentinfo.MODIFY_TOURNAMENT, parameters: { teamid: team.id, name: team.name, onSuccess: successCB, onError: errorCB }, state: __store.getState() }); } export function startMatch(matchId, successCallback, errorCallback) { __store.dispatch({ type: actionTypesTournamentinfo.START_MATCH, parameters: { matchId: matchId, successCallback: successCallback, errorCallback: errorCallback }, state: __store.getState() }); } export function endMatch(matchId, successCallback, errorCallback) { __store.dispatch({ type: actionTypesTournamentinfo.END_MATCH, parameters: { matchId: matchId, successCallback: successCallback, errorCallback: errorCallback }, state: __store.getState() }); } export function getState() { return __store.getState(); } export function requestTournamentList(type, successCallback, errorCallback) { __store.dispatch({ type: actionTypesTournamentlist.FETCH, parameters: { type: type, successCallback: successCallback, errorCallback: errorCallback }, state: __store.getState() }); } function rehydrateApplicationState() { const persistedState = localStorage.getItem('reduxState') ? JSON.parse(localStorage.getItem('reduxState')) : undefined; if (persistedState) { __store.dispatch({ type: actionTypesUserinfo.REHYDRATE, parameters: Object.assign({}, persistedState.userinfo) }); __store.dispatch({ type: actionTypesTournamentinfo.REHYDRATE, parameters: Object.assign({}, persistedState.tournamentinfo) }); __store.dispatch({ type: actionTypesTournamentlist.REHYDRATE, parameters: Object.assign({}, persistedState.tournamentlist) }); __store.dispatch({ type: actionTypesTournamentStatistics.REHYDRATE, parameters: Object.assign({}, persistedState.tournamentstatistics) }); applicationHydrated = true; } }