Restructure redux code (#109)

* Restructure redux code

Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de>
Co-authored-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
mrdrogdrog 2020-05-31 22:51:36 +02:00 committed by GitHub
parent db5bec7000
commit 570c45017c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 214 additions and 231 deletions

View file

@ -0,0 +1,8 @@
import { expectResponseCode, getBackendUrl } from '../../utils/apiUtils'
import { BackendConfig } from './types'
export const getBackendConfig = async (): Promise<BackendConfig> => {
const response = await fetch(getBackendUrl() + '/config')
expectResponseCode(response)
return await response.json() as Promise<BackendConfig>
}

View file

@ -0,0 +1,39 @@
export interface BackendConfig {
allowAnonymous: boolean,
authProviders: AuthProvidersState,
customAuthNames: CustomAuthNames,
specialLinks: SpecialLinks,
version: BackendVersion,
}
export interface BackendVersion {
version: string,
sourceCodeUrl: string
issueTrackerUrl: string
}
export interface AuthProvidersState {
facebook: boolean,
github: boolean,
twitter: boolean,
gitlab: boolean,
dropbox: boolean,
ldap: boolean,
google: boolean,
saml: boolean,
oauth2: boolean,
email: boolean,
openid: boolean,
}
export interface CustomAuthNames {
ldap: string;
oauth2: string;
saml: string;
}
export interface SpecialLinks {
privacy: string,
termsOfUse: string,
imprint: string,
}

View file

@ -1,15 +0,0 @@
import { FrontendConfigState } from '../redux/frontend-config/types'
import { BackendConfigState } from '../redux/backend-config/types'
import { expectResponseCode, getBackendUrl } from '../utils/apiUtils'
export const getBackendConfig = async (): Promise<BackendConfigState> => {
const response = await fetch(getBackendUrl() + '/config')
expectResponseCode(response)
return await response.json() as Promise<BackendConfigState>
}
export const getFrontendConfig = async (baseUrl: string): Promise<FrontendConfigState> => {
const response = await fetch(`${baseUrl}config.json`)
expectResponseCode(response)
return await response.json() as Promise<FrontendConfigState>
}

View file

@ -0,0 +1,8 @@
import { expectResponseCode } from '../../utils/apiUtils'
import { FrontendConfig } from './types'
export const getFrontendConfig = async (baseUrl: string): Promise<FrontendConfig> => {
const response = await fetch(`${baseUrl}config.json`)
expectResponseCode(response)
return await response.json() as Promise<FrontendConfig>
}

View file

@ -0,0 +1,3 @@
export interface FrontendConfig {
backendUrl: string
}

View file

@ -2,9 +2,9 @@ import React from 'react'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { useParams } from 'react-router' import { useParams } from 'react-router'
import { ApplicationState } from '../../redux' import { ApplicationState } from '../../redux'
import { EditorMode } from '../../redux/editor/types'
import { EditorWindow } from './editor-window/editor-window' import { EditorWindow } from './editor-window/editor-window'
import { MarkdownPreview } from './markdown-preview/markdown-preview' import { MarkdownPreview } from './markdown-preview/markdown-preview'
import { EditorMode } from './task-bar/editor-view-mode'
import { TaskBar } from './task-bar/task-bar' import { TaskBar } from './task-bar/task-bar'
interface RouteParameters { interface RouteParameters {

View file

@ -1,13 +1,18 @@
import { ToggleButton, ToggleButtonGroup } from 'react-bootstrap'
import React from 'react' import React from 'react'
import { ToggleButton, ToggleButtonGroup } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { ForkAwesomeIcon } from '../../../fork-awesome/fork-awesome-icon' import { ForkAwesomeIcon } from '../../../fork-awesome/fork-awesome-icon'
import { ApplicationState } from '../../../redux' import { ApplicationState } from '../../../redux'
import { EditorMode } from '../../../redux/editor/types'
import { setEditorModeConfig } from '../../../redux/editor/methods' import { setEditorModeConfig } from '../../../redux/editor/methods'
import { useTranslation } from 'react-i18next'
const EditorViewMode: React.FC = () => { export enum EditorMode {
PREVIEW,
BOTH,
EDITOR,
}
export const EditorViewMode: React.FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const editorConfig = useSelector((state: ApplicationState) => state.editorConfig) const editorConfig = useSelector((state: ApplicationState) => state.editorConfig)
return ( return (
@ -15,7 +20,9 @@ const EditorViewMode: React.FC = () => {
type="radio" type="radio"
name="options" name="options"
defaultValue={editorConfig.editorMode} defaultValue={editorConfig.editorMode}
onChange={(value: EditorMode) => { setEditorModeConfig(value) }}> onChange={(value: EditorMode) => {
setEditorModeConfig(value)
}}>
<ToggleButton value={EditorMode.PREVIEW} variant="outline-secondary" title={t('editor.viewMode.view')}> <ToggleButton value={EditorMode.PREVIEW} variant="outline-secondary" title={t('editor.viewMode.view')}>
<ForkAwesomeIcon icon="eye"/> <ForkAwesomeIcon icon="eye"/>
</ToggleButton> </ToggleButton>
@ -28,5 +35,3 @@ const EditorViewMode: React.FC = () => {
</ToggleButtonGroup> </ToggleButtonGroup>
) )
} }
export { EditorViewMode }

View file

@ -3,7 +3,6 @@ import { Navbar } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { ApplicationState } from '../../../../../redux' import { ApplicationState } from '../../../../../redux'
import { LoginStatus } from '../../../../../redux/user/types'
import { HeaderNavLink } from '../header-nav-link' import { HeaderNavLink } from '../header-nav-link'
import { NewGuestNoteButton } from '../new-guest-note-button' import { NewGuestNoteButton } from '../new-guest-note-button'
import { NewUserNoteButton } from '../new-user-note-button' import { NewUserNoteButton } from '../new-user-note-button'
@ -26,7 +25,7 @@ const HeaderBar: React.FC = () => {
</HeaderNavLink> </HeaderNavLink>
</div> </div>
<div className="d-inline-flex"> <div className="d-inline-flex">
{user.status === LoginStatus.forbidden {!user
? <Fragment> ? <Fragment>
<span className={'mr-1 d-flex'}> <span className={'mr-1 d-flex'}>
<NewGuestNoteButton/> <NewGuestNoteButton/>

View file

@ -1,17 +1,21 @@
import { Dropdown } from 'react-bootstrap'
import React from 'react' import React from 'react'
import { Dropdown } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { LinkContainer } from 'react-router-bootstrap'
import { ForkAwesomeIcon } from '../../../../../fork-awesome/fork-awesome-icon' import { ForkAwesomeIcon } from '../../../../../fork-awesome/fork-awesome-icon'
import { ApplicationState } from '../../../../../redux' import { ApplicationState } from '../../../../../redux'
import { LinkContainer } from 'react-router-bootstrap'
import { clearUser } from '../../../../../redux/user/methods' import { clearUser } from '../../../../../redux/user/methods'
import { Trans, useTranslation } from 'react-i18next'
import { UserAvatar } from '../../user-avatar/user-avatar' import { UserAvatar } from '../../user-avatar/user-avatar'
export const UserDropdown: React.FC = () => { export const UserDropdown: React.FC = () => {
useTranslation() useTranslation()
const user = useSelector((state: ApplicationState) => state.user) const user = useSelector((state: ApplicationState) => state.user)
if (!user) {
return null
}
return ( return (
<Dropdown alignRight> <Dropdown alignRight>
<Dropdown.Toggle size="sm" variant="dark" id="dropdown-user" className={'d-flex align-items-center'}> <Dropdown.Toggle size="sm" variant="dark" id="dropdown-user" className={'d-flex align-items-center'}>

View file

@ -1,9 +1,8 @@
import React from 'react' import React from 'react'
import { Link } from 'react-router-dom'
import { Button } from 'react-bootstrap' import { Button } from 'react-bootstrap'
import { LoginStatus } from '../../../../../redux/user/types'
import { Trans, useTranslation } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { Link } from 'react-router-dom'
import { ApplicationState } from '../../../../../redux' import { ApplicationState } from '../../../../../redux'
import './cover-buttons.scss' import './cover-buttons.scss'
@ -11,7 +10,7 @@ export const CoverButtons: React.FC = () => {
useTranslation() useTranslation()
const user = useSelector((state: ApplicationState) => state.user) const user = useSelector((state: ApplicationState) => state.user)
if (user.status === LoginStatus.ok) { if (user) {
return null return null
} }

View file

@ -1,20 +1,19 @@
import React from 'react' import React from 'react'
import { Card, Col, Row } from 'react-bootstrap' import { Card, Col, Row } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import { ViaEMail } from './auth/via-email'
import { OneClickType, ViaOneClick } from './auth/via-one-click'
import { ViaLdap } from './auth/via-ldap'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { ApplicationState } from '../../../../redux'
import { ViaOpenId } from './auth/via-openid'
import { Redirect } from 'react-router' import { Redirect } from 'react-router'
import { LoginStatus } from '../../../../redux/user/types' import { ApplicationState } from '../../../../redux'
import { ViaEMail } from './auth/via-email'
import { ViaLdap } from './auth/via-ldap'
import { OneClickType, ViaOneClick } from './auth/via-one-click'
import { ViaOpenId } from './auth/via-openid'
export const Login: React.FC = () => { export const Login: React.FC = () => {
useTranslation() useTranslation()
const authProviders = useSelector((state: ApplicationState) => state.backendConfig.authProviders) const authProviders = useSelector((state: ApplicationState) => state.backendConfig.authProviders)
const customAuthNames = useSelector((state: ApplicationState) => state.backendConfig.customAuthNames) const customAuthNames = useSelector((state: ApplicationState) => state.backendConfig.customAuthNames)
const userLoginState = useSelector((state: ApplicationState) => state.user.status) const userLoginState = useSelector((state: ApplicationState) => state.user)
const emailForm = authProviders.email ? <ViaEMail/> : null const emailForm = authProviders.email ? <ViaEMail/> : null
const ldapForm = authProviders.ldap ? <ViaLdap/> : null const ldapForm = authProviders.ldap ? <ViaLdap/> : null
const openIdForm = authProviders.openid ? <ViaOpenId/> : null const openIdForm = authProviders.openid ? <ViaOpenId/> : null
@ -30,7 +29,7 @@ export const Login: React.FC = () => {
} }
} }
if (userLoginState === LoginStatus.ok) { if (userLoginState) {
// TODO Redirect to previous page? // TODO Redirect to previous page?
return ( return (
<Redirect to='/history'/> <Redirect to='/history'/>

View file

@ -3,7 +3,7 @@ import { Col, Row } from 'react-bootstrap'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { Redirect } from 'react-router' import { Redirect } from 'react-router'
import { ApplicationState } from '../../../../redux' import { ApplicationState } from '../../../../redux'
import { LoginProvider, LoginStatus } from '../../../../redux/user/types' import { LoginProvider } from '../../../../redux/user/types'
import { ProfileAccountManagement } from './settings/profile-account-management' import { ProfileAccountManagement } from './settings/profile-account-management'
import { ProfileChangePassword } from './settings/profile-change-password' import { ProfileChangePassword } from './settings/profile-change-password'
import { ProfileDisplayName } from './settings/profile-display-name' import { ProfileDisplayName } from './settings/profile-display-name'
@ -11,9 +11,9 @@ import { ProfileDisplayName } from './settings/profile-display-name'
export const Profile: React.FC = () => { export const Profile: React.FC = () => {
const user = useSelector((state: ApplicationState) => state.user) const user = useSelector((state: ApplicationState) => state.user)
if (user.status === LoginStatus.forbidden) { if (!user) {
return ( return (
<Redirect to={'/login'} /> <Redirect to={'/login'}/>
) )
} }
@ -22,7 +22,7 @@ export const Profile: React.FC = () => {
<Row className="h-100 flex justify-content-center"> <Row className="h-100 flex justify-content-center">
<Col lg={6}> <Col lg={6}>
<ProfileDisplayName/> <ProfileDisplayName/>
{ user.provider === LoginProvider.EMAIL ? <ProfileChangePassword/> : null } {user.provider === LoginProvider.EMAIL ? <ProfileChangePassword/> : null}
<ProfileAccountManagement/> <ProfileAccountManagement/>
</Col> </Col>
</Row> </Row>

View file

@ -1,5 +1,5 @@
import React, { ChangeEvent, FormEvent, useState } from 'react' import React, { ChangeEvent, FormEvent, useEffect, useState } from 'react'
import { Button, Card, Form } from 'react-bootstrap' import { Alert, Button, Card, Form } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { updateDisplayName } from '../../../../../api/me' import { updateDisplayName } from '../../../../../api/me'
@ -7,13 +7,22 @@ import { ApplicationState } from '../../../../../redux'
import { getAndSetUser } from '../../../../../utils/apiUtils' import { getAndSetUser } from '../../../../../utils/apiUtils'
export const ProfileDisplayName: React.FC = () => { export const ProfileDisplayName: React.FC = () => {
const regexInvalidDisplayName = /^\s*$/
const { t } = useTranslation() const { t } = useTranslation()
const user = useSelector((state: ApplicationState) => state.user) const user = useSelector((state: ApplicationState) => state.user)
const [submittable, setSubmittable] = useState(false) const [submittable, setSubmittable] = useState(false)
const [error, setError] = useState(false) const [error, setError] = useState(false)
const [displayName, setDisplayName] = useState(user.name) const [displayName, setDisplayName] = useState('')
const regexInvalidDisplayName = /^\s*$/ useEffect(() => {
if (user) {
setDisplayName(user.name)
}
}, [user])
if (!user) {
return <Alert variant={'danger'}>User not logged in</Alert>
}
const changeNameField = (event: ChangeEvent<HTMLInputElement>) => { const changeNameField = (event: ChangeEvent<HTMLInputElement>) => {
setSubmittable(!regexInvalidDisplayName.test(event.target.value)) setSubmittable(!regexInvalidDisplayName.test(event.target.value))

View file

@ -1,6 +1,7 @@
import { getBackendConfig, getFrontendConfig } from '../api/config' import { getBackendConfig } from '../api/backend-config'
import { setFrontendConfig } from '../redux/frontend-config/methods' import { getFrontendConfig } from '../api/frontend-config'
import { setBackendConfig } from '../redux/backend-config/methods' import { setBackendConfig } from '../redux/backend-config/methods'
import { setFrontendConfig } from '../redux/frontend-config/methods'
import { getAndSetUser } from '../utils/apiUtils' import { getAndSetUser } from '../utils/apiUtils'
export const loadAllConfig: (baseUrl: string) => Promise<void> = async (baseUrl) => { export const loadAllConfig: (baseUrl: string) => Promise<void> = async (baseUrl) => {

View file

@ -1,12 +1,11 @@
import { BackendConfigState, SET_BACKEND_CONFIG_ACTION_TYPE, SetBackendConfigAction } from './types' import { BackendConfig } from '../../api/backend-config/types'
import { store } from '../../utils/store' import { store } from '../../utils/store'
import { BackendConfigActionType, SetBackendConfigAction } from './types'
export const setBackendConfig = (state: BackendConfigState): void => { export const setBackendConfig = (state: BackendConfig): void => {
const action: SetBackendConfigAction = { const action: SetBackendConfigAction = {
type: SET_BACKEND_CONFIG_ACTION_TYPE, type: BackendConfigActionType.SET_BACKEND_CONFIG,
payload: { state: state
state
}
} }
store.dispatch(action) store.dispatch(action)
} }

View file

@ -1,7 +1,8 @@
import { Reducer } from 'redux' import { Reducer } from 'redux'
import { BackendConfigActions, BackendConfigState, SET_BACKEND_CONFIG_ACTION_TYPE } from './types' import { BackendConfig } from '../../api/backend-config/types'
import { BackendConfigActions, BackendConfigActionType, SetBackendConfigAction } from './types'
export const initialState: BackendConfigState = { export const initialState: BackendConfig = {
allowAnonymous: true, allowAnonymous: true,
authProviders: { authProviders: {
facebook: false, facebook: false,
@ -33,10 +34,10 @@ export const initialState: BackendConfigState = {
} }
} }
export const BackendConfigReducer: Reducer<BackendConfigState, BackendConfigActions> = (state: BackendConfigState = initialState, action: BackendConfigActions) => { export const BackendConfigReducer: Reducer<(BackendConfig), BackendConfigActions> = (state: (BackendConfig) = initialState, action: BackendConfigActions) => {
switch (action.type) { switch (action.type) {
case SET_BACKEND_CONFIG_ACTION_TYPE: case BackendConfigActionType.SET_BACKEND_CONFIG:
return action.payload.state return (action as SetBackendConfigAction).state
default: default:
return state return state
} }

View file

@ -1,52 +1,14 @@
import { Action } from 'redux' import { Action } from 'redux'
import { BackendConfig } from '../../api/backend-config/types'
export const SET_BACKEND_CONFIG_ACTION_TYPE = 'backend-config/set' export enum BackendConfigActionType {
SET_BACKEND_CONFIG = 'backend-config/set'
export interface BackendConfigState {
allowAnonymous: boolean,
authProviders: AuthProvidersState,
customAuthNames: CustomAuthNames,
specialLinks: SpecialLinks,
version: BackendVersion,
} }
export interface BackendVersion { export interface BackendConfigActions extends Action<BackendConfigActionType>{
version: string, type: BackendConfigActionType;
sourceCodeUrl: string
issueTrackerUrl: string
} }
export interface AuthProvidersState { export interface SetBackendConfigAction extends BackendConfigActions {
facebook: boolean, state: BackendConfig;
github: boolean,
twitter: boolean,
gitlab: boolean,
dropbox: boolean,
ldap: boolean,
google: boolean,
saml: boolean,
oauth2: boolean,
email: boolean,
openid: boolean,
} }
export interface CustomAuthNames {
ldap: string;
oauth2: string;
saml: string;
}
export interface SpecialLinks {
privacy: string,
termsOfUse: string,
imprint: string,
}
export interface SetBackendConfigAction extends Action {
type: string;
payload: {
state: BackendConfigState;
};
}
export type BackendConfigActions = SetBackendConfigAction;

View file

@ -1,14 +1,11 @@
import { import { EditorMode } from '../../components/editor/task-bar/editor-view-mode'
EditorMode,
SET_EDITOR_CONFIG_MODE_ACTION_TYPE,
SetEditorConfigAction
} from './types'
import { store } from '../../utils/store' import { store } from '../../utils/store'
import { EditorConfigActionType, SetEditorConfigAction } from './types'
export const setEditorModeConfig = (editorMode: EditorMode): void => { export const setEditorModeConfig = (editorMode: EditorMode): void => {
const action: SetEditorConfigAction = { const action: SetEditorConfigAction = {
type: SET_EDITOR_CONFIG_MODE_ACTION_TYPE, type: EditorConfigActionType.SET_EDITOR_VIEW_MODE,
payload: editorMode mode: editorMode
} }
store.dispatch(action) store.dispatch(action)
} }

View file

@ -1,21 +1,17 @@
import { Reducer } from 'redux' import { Reducer } from 'redux'
import { import { EditorMode } from '../../components/editor/task-bar/editor-view-mode'
EditorConfigActions, import { EditorConfig, EditorConfigActions, EditorConfigActionType, SetEditorConfigAction } from './types'
EditorConfigState,
EditorMode,
SET_EDITOR_CONFIG_MODE_ACTION_TYPE
} from './types'
export const initialState: EditorConfigState = { export const initialState: EditorConfig = {
editorMode: EditorMode.PREVIEW editorMode: EditorMode.PREVIEW
} }
export const EditorConfigReducer: Reducer<EditorConfigState, EditorConfigActions> = (state: EditorConfigState = initialState, action: EditorConfigActions) => { export const EditorConfigReducer: Reducer<EditorConfig, EditorConfigActions> = (state: EditorConfig = initialState, action: EditorConfigActions) => {
switch (action.type) { switch (action.type) {
case SET_EDITOR_CONFIG_MODE_ACTION_TYPE: case EditorConfigActionType.SET_EDITOR_VIEW_MODE:
return { return {
...state, ...state,
editorMode: action.payload editorMode: (action as SetEditorConfigAction).mode
} }
default: default:
return state return state

View file

@ -1,20 +1,18 @@
import { Action } from 'redux' import { Action } from 'redux'
import { EditorMode } from '../../components/editor/task-bar/editor-view-mode'
export const SET_EDITOR_CONFIG_MODE_ACTION_TYPE = 'editor/mode/set' export enum EditorConfigActionType {
SET_EDITOR_VIEW_MODE = 'editor/mode/set'
}
export interface EditorConfigState { export interface EditorConfig {
editorMode: EditorMode; editorMode: EditorMode;
} }
export enum EditorMode { export interface EditorConfigActions extends Action<EditorConfigActionType> {
PREVIEW, type: EditorConfigActionType;
BOTH,
EDITOR,
} }
export interface SetEditorConfigAction extends Action { export interface SetEditorConfigAction extends EditorConfigActions {
type: string; mode: EditorMode;
payload: EditorMode;
} }
export type EditorConfigActions = SetEditorConfigAction;

View file

@ -1,15 +1,14 @@
import { FrontendConfigState, SET_FRONTEND_CONFIG_ACTION_TYPE, SetFrontendConfigAction } from './types' import { FrontendConfig } from '../../api/frontend-config/types'
import { store } from '../../utils/store' import { store } from '../../utils/store'
import { FrontendConfigActionType, SetFrontendConfigAction } from './types'
export const setFrontendConfig = (state: FrontendConfigState): void => { export const setFrontendConfig = (state: FrontendConfig): void => {
const action: SetFrontendConfigAction = { const action: SetFrontendConfigAction = {
type: SET_FRONTEND_CONFIG_ACTION_TYPE, type: FrontendConfigActionType.SET_FRONTEND_CONFIG,
payload: {
state: { state: {
...state, ...state,
backendUrl: state.backendUrl + '/api/v2.0/' backendUrl: state.backendUrl + '/api/v2.0/'
} }
} }
}
store.dispatch(action) store.dispatch(action)
} }

View file

@ -1,14 +1,15 @@
import { Reducer } from 'redux' import { Reducer } from 'redux'
import { FrontendConfigActions, FrontendConfigState, SET_FRONTEND_CONFIG_ACTION_TYPE } from './types' import { FrontendConfig } from '../../api/frontend-config/types'
import { FrontendConfigActions, FrontendConfigActionType, SetFrontendConfigAction } from './types'
export const initialState: FrontendConfigState = { const initialState: FrontendConfig = {
backendUrl: '' backendUrl: ''
} }
export const FrontendConfigReducer: Reducer<FrontendConfigState, FrontendConfigActions> = (state: FrontendConfigState = initialState, action: FrontendConfigActions) => { export const FrontendConfigReducer: Reducer<(FrontendConfig), FrontendConfigActions> = (state: (FrontendConfig) = initialState, action: FrontendConfigActions) => {
switch (action.type) { switch (action.type) {
case SET_FRONTEND_CONFIG_ACTION_TYPE: case FrontendConfigActionType.SET_FRONTEND_CONFIG:
return action.payload.state return (action as SetFrontendConfigAction).state
default: default:
return state return state
} }

View file

@ -1,16 +1,14 @@
import { Action } from 'redux' import { Action } from 'redux'
import { FrontendConfig } from '../../api/frontend-config/types'
export const SET_FRONTEND_CONFIG_ACTION_TYPE = 'frontend-config/set' export enum FrontendConfigActionType {
SET_FRONTEND_CONFIG = 'frontend-config/set'
export interface SetFrontendConfigAction extends Action {
type: string;
payload: {
state: FrontendConfigState;
};
} }
export interface FrontendConfigState { export interface FrontendConfigActions extends Action<FrontendConfigActionType> {
backendUrl: string, type: FrontendConfigActionType;
} }
export type FrontendConfigActions = SetFrontendConfigAction; export interface SetFrontendConfigAction extends FrontendConfigActions {
state: FrontendConfig;
}

View file

@ -1,18 +1,18 @@
import { combineReducers, Reducer } from 'redux' import { combineReducers, Reducer } from 'redux'
import { UserState } from './user/types' import { BackendConfig } from '../api/backend-config/types'
import { UserReducer } from './user/reducers' import { FrontendConfig } from '../api/frontend-config/types'
import { BackendConfigState } from './backend-config/types'
import { FrontendConfigState } from './frontend-config/types'
import { BackendConfigReducer } from './backend-config/reducers' import { BackendConfigReducer } from './backend-config/reducers'
import { FrontendConfigReducer } from './frontend-config/reducers'
import { EditorConfigState } from './editor/types'
import { EditorConfigReducer } from './editor/reducers' import { EditorConfigReducer } from './editor/reducers'
import { EditorConfig } from './editor/types'
import { FrontendConfigReducer } from './frontend-config/reducers'
import { UserReducer } from './user/reducers'
import { MaybeUserState } from './user/types'
export interface ApplicationState { export interface ApplicationState {
user: UserState; user: MaybeUserState;
backendConfig: BackendConfigState; backendConfig: BackendConfig;
frontendConfig: FrontendConfigState; frontendConfig: FrontendConfig;
editorConfig: EditorConfigState; editorConfig: EditorConfig;
} }
export const allReducers: Reducer<ApplicationState> = combineReducers<ApplicationState>({ export const allReducers: Reducer<ApplicationState> = combineReducers<ApplicationState>({

View file

@ -1,20 +1,17 @@
import { CLEAR_USER_ACTION_TYPE, ClearUserAction, SET_USER_ACTION_TYPE, SetUserAction, UserState } from './types'
import { store } from '../../utils/store' import { store } from '../../utils/store'
import { ClearUserAction, SetUserAction, UserActionType, UserState } from './types'
export const setUser: (state: UserState) => void = (state: UserState) => { export const setUser: (state: UserState) => void = (state: UserState) => {
const action: SetUserAction = { const action: SetUserAction = {
type: SET_USER_ACTION_TYPE, type: UserActionType.SET_USER,
payload: {
state state
} }
}
store.dispatch(action) store.dispatch(action)
} }
export const clearUser: () => void = () => { export const clearUser: () => void = () => {
const action: ClearUserAction = { const action: ClearUserAction = {
type: CLEAR_USER_ACTION_TYPE, type: UserActionType.CLEAR_USER
payload: null
} }
store.dispatch(action) store.dispatch(action)
} }

View file

@ -1,28 +1,12 @@
import { Reducer } from 'redux' import { Reducer } from 'redux'
import { import { MaybeUserState, SetUserAction, UserActions, UserActionType } from './types'
CLEAR_USER_ACTION_TYPE,
LoginProvider,
LoginStatus,
SET_USER_ACTION_TYPE,
SetUserAction,
UserActions,
UserState
} from './types'
export const initialState: UserState = { export const UserReducer: Reducer<MaybeUserState, UserActions> = (state: MaybeUserState = null, action: UserActions) => {
id: '',
name: '',
photo: '',
status: LoginStatus.forbidden,
provider: LoginProvider.EMAIL
}
export const UserReducer: Reducer<UserState, UserActions> = (state: UserState = initialState, action: UserActions) => {
switch (action.type) { switch (action.type) {
case SET_USER_ACTION_TYPE: case UserActionType.SET_USER:
return (action as SetUserAction).payload.state return (action as SetUserAction).state
case CLEAR_USER_ACTION_TYPE: case UserActionType.CLEAR_USER:
return initialState return null
default: default:
return state return state
} }

View file

@ -1,33 +1,27 @@
import { Action } from 'redux' import { Action } from 'redux'
export const SET_USER_ACTION_TYPE = 'user/set' export enum UserActionType {
export const CLEAR_USER_ACTION_TYPE = 'user/clear' SET_USER = 'user/set',
CLEAR_USER = 'user/clear'
export interface SetUserAction extends Action {
type: string;
payload: {
state: UserState,
};
} }
export interface ClearUserAction extends Action { export interface UserActions extends Action<UserActionType> {
type: string; type: UserActionType;
payload: null;
} }
export interface SetUserAction extends UserActions {
state: UserState;
}
export type ClearUserAction = UserActions
export interface UserState { export interface UserState {
status: LoginStatus;
id: string; id: string;
name: string; name: string;
photo: string; photo: string;
provider: LoginProvider; provider: LoginProvider;
} }
export enum LoginStatus {
forbidden = 'forbidden',
ok = 'ok'
}
export enum LoginProvider { export enum LoginProvider {
FACEBOOK = 'facebook', FACEBOOK = 'facebook',
GITHUB = 'github', GITHUB = 'github',
@ -42,4 +36,4 @@ export enum LoginProvider {
OPENID = 'openid' OPENID = 'openid'
} }
export type UserActions = SetUserAction | ClearUserAction; export type MaybeUserState = (UserState | null)

View file

@ -1,12 +1,10 @@
import { getMe } from '../api/me' import { getMe } from '../api/me'
import { setUser } from '../redux/user/methods' import { setUser } from '../redux/user/methods'
import { LoginStatus } from '../redux/user/types'
import { store } from './store' import { store } from './store'
export const getAndSetUser: () => (Promise<void>) = async () => { export const getAndSetUser: () => (Promise<void>) = async () => {
const me = await getMe() const me = await getMe()
setUser({ setUser({
status: LoginStatus.ok,
id: me.id, id: me.id,
name: me.name, name: me.name,
photo: me.photo, photo: me.photo,