mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-24 18:56:32 -05:00
Frontend config and Loader component (#12)
* Add FrontendConfig and Loader Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Merge more setup into the application loader Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Rename config files Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Remove blank line Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Fix url Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Make strings more specific Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Restructure store use Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * split methods and actions Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * extract code Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * remove actions.ts Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * add reason and rename component Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * remove unused call Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Use redux store in api Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * activate email in backend config Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * add new line Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * reduce code Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Make error more specific Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Use expectedResponseCode Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Update src/redux/backend-config/types.ts Co-authored-by: Philip Molares <git@molar.es> * Update src/components/application-loader/application-loader.tsx Co-authored-by: Philip Molares <git@molar.es> * Update src/components/application-loader/application-loader.tsx Co-authored-by: Philip Molares <git@molar.es> * Update src/components/application-loader/application-loader.tsx Co-authored-by: Philip Molares <git@molar.es> * Use fragment Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> Co-authored-by: Philip Molares <git@molar.es>
This commit is contained in:
parent
ef17c7acbb
commit
a490e1240b
36 changed files with 425 additions and 256 deletions
3
public/config.json
Normal file
3
public/config.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"backendUrl": "http://localhost:3000"
|
||||
}
|
|
@ -1,3 +1,15 @@
|
|||
export const getConfig = async () => {
|
||||
return fetch('/config');
|
||||
import {FrontendConfigState} from "../redux/frontend-config/types";
|
||||
import {BackendConfigState} from "../redux/backend-config/types";
|
||||
import {expectResponseCode, getBackendUrl} from "../utils/apiUtils";
|
||||
|
||||
export const getBackendConfig = async () => {
|
||||
return fetch(getBackendUrl() + '/backend-config.json')
|
||||
.then(expectResponseCode())
|
||||
.then(response => response.json() as Promise<BackendConfigState>);
|
||||
}
|
||||
|
||||
export const getFrontendConfig = async () => {
|
||||
return fetch(getBackendUrl() + '/config.json')
|
||||
.then(expectResponseCode())
|
||||
.then(response => response.json() as Promise<FrontendConfigState>);
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
import {expectResponseCode, getBackendUrl} from "../utils/apiUtils";
|
||||
|
||||
export const getMe = async () => {
|
||||
return fetch('/me');
|
||||
}
|
||||
|
||||
export const postEmailLogin = async (email: string, password: string) => {
|
||||
return fetch("/login", {
|
||||
return fetch(getBackendUrl() + "/login", {
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
cache: 'no-cache',
|
||||
|
@ -17,11 +19,7 @@ export const postEmailLogin = async (email: string, password: string) => {
|
|||
email: email,
|
||||
password: password,
|
||||
})
|
||||
}).then(response => {
|
||||
if (response.status !== 200) {
|
||||
return Promise.reject("Response code not 200");
|
||||
} else {
|
||||
return response.json();
|
||||
}
|
||||
})
|
||||
.then(expectResponseCode())
|
||||
.then(response => response.json());
|
||||
}
|
80
src/components/application-loader/application-loader.scss
Normal file
80
src/components/application-loader/application-loader.scss
Normal file
|
@ -0,0 +1,80 @@
|
|||
.loader {
|
||||
|
||||
.animation-pulse {
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.animation-shake {
|
||||
animation: shake 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
|
||||
&.middle, .middle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.progress {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(1, 1);
|
||||
filter: drop-shadow(0 0 0px black);
|
||||
}
|
||||
10% {
|
||||
transform: scale(1.5, 1.5);
|
||||
filter: drop-shadow(0 0 100px white);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1, 1);
|
||||
filter: drop-shadow(0 0 0px black);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
0% {
|
||||
transform: translate(1px, 1px) rotate(0deg);
|
||||
}
|
||||
10% {
|
||||
transform: translate(-1px, -2px) rotate(-1deg);
|
||||
}
|
||||
20% {
|
||||
transform: translate(-3px, 0px) rotate(1deg);
|
||||
}
|
||||
30% {
|
||||
transform: translate(3px, 2px) rotate(0deg);
|
||||
}
|
||||
40% {
|
||||
transform: translate(1px, -1px) rotate(1deg);
|
||||
}
|
||||
50% {
|
||||
transform: translate(-1px, 2px) rotate(-1deg);
|
||||
}
|
||||
60% {
|
||||
transform: translate(-3px, 1px) rotate(0deg);
|
||||
}
|
||||
70% {
|
||||
transform: translate(3px, 1px) rotate(-1deg);
|
||||
}
|
||||
80% {
|
||||
transform: translate(-1px, -1px) rotate(1deg);
|
||||
}
|
||||
90% {
|
||||
transform: translate(1px, 2px) rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate(1px, -2px) rotate(-1deg);
|
||||
}
|
||||
}
|
||||
}
|
41
src/components/application-loader/application-loader.tsx
Normal file
41
src/components/application-loader/application-loader.tsx
Normal file
|
@ -0,0 +1,41 @@
|
|||
import React, {Fragment, useEffect, useState} from "react";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import "./application-loader.scss";
|
||||
import {Alert} from "react-bootstrap";
|
||||
|
||||
interface ApplicationLoaderProps {
|
||||
initTasks: Promise<any>[]
|
||||
}
|
||||
|
||||
export const ApplicationLoader: React.FC<ApplicationLoaderProps> = ({children, initTasks}) => {
|
||||
const [failed, setFailed] = useState<boolean>(false);
|
||||
const [doneTasks, setDoneTasks] = useState<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
setDoneTasks(0);
|
||||
initTasks.map(task =>
|
||||
task.then(() =>
|
||||
setDoneTasks(prevDoneTasks => {
|
||||
return prevDoneTasks + 1;
|
||||
}))
|
||||
.catch((reason) => {
|
||||
setFailed(true);
|
||||
console.error(reason);
|
||||
})
|
||||
)
|
||||
}, [initTasks]);
|
||||
|
||||
return (<Fragment>{
|
||||
doneTasks < initTasks.length || initTasks.length === 0 ? (
|
||||
<div className="loader middle">
|
||||
<div className="icon">
|
||||
<FontAwesomeIcon icon="file-alt" size="6x"
|
||||
className={failed ? "animation-shake" : "animation-pulse"}/>
|
||||
</div>
|
||||
{
|
||||
failed ? <Alert variant={"danger"}>An error occured while loading the application!</Alert> : null
|
||||
}
|
||||
</div>
|
||||
) : children
|
||||
}</Fragment>);
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
import {useDispatch} from "react-redux";
|
||||
import React from "react";
|
||||
import {getConfig} from "../../api/config";
|
||||
import {ApplicationConfigState} from "../../redux/application-config/types";
|
||||
import {setApplicationConfig} from "../../redux/application-config/actions";
|
||||
|
||||
const InitializeConfigStateFromApi: React.FC = () => {
|
||||
const dispatch = useDispatch();
|
||||
getConfig()
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
return (response.json() as Promise<ApplicationConfigState>);
|
||||
}
|
||||
})
|
||||
.then(config => {
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
dispatch(setApplicationConfig({
|
||||
allowAnonymous: config.allowAnonymous,
|
||||
authProviders: {
|
||||
facebook: config.authProviders.facebook,
|
||||
github: config.authProviders.github,
|
||||
twitter: config.authProviders.twitter,
|
||||
gitlab: config.authProviders.gitlab,
|
||||
dropbox: config.authProviders.dropbox,
|
||||
ldap: config.authProviders.ldap,
|
||||
google: config.authProviders.google,
|
||||
saml: config.authProviders.saml,
|
||||
oauth2: config.authProviders.oauth2,
|
||||
email: config.authProviders.email
|
||||
},
|
||||
specialLinks: {
|
||||
privacy: config.specialLinks.privacy,
|
||||
termsOfUse: config.specialLinks.termsOfUse,
|
||||
imprint: config.specialLinks.imprint,
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export { InitializeConfigStateFromApi }
|
|
@ -1,35 +0,0 @@
|
|||
import {useDispatch} from "react-redux";
|
||||
import {getMe} from "../../api/user";
|
||||
import {setUser} from "../../redux/user/actions";
|
||||
import {LoginStatus, UserState} from "../../redux/user/types";
|
||||
import React from "react";
|
||||
import {Dispatch} from "redux";
|
||||
|
||||
export const getAndSetUser = (dispatch: Dispatch<any>) => {
|
||||
getMe()
|
||||
.then((me) => {
|
||||
if (me.status === 200) {
|
||||
return (me.json() as Promise<UserState>);
|
||||
}
|
||||
})
|
||||
.then(user => {
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
dispatch(setUser({
|
||||
status: LoginStatus.ok,
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
photo: user.photo,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
const InitializeUserStateFromApi: React.FC = () => {
|
||||
const dispatch = useDispatch();
|
||||
getAndSetUser(dispatch);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export { InitializeUserStateFromApi }
|
|
@ -18,7 +18,7 @@ const PoweredByLinks: React.FC = () => {
|
|||
}
|
||||
]
|
||||
|
||||
const config = useSelector((state: ApplicationState) => state.applicationConfig);
|
||||
const config = useSelector((state: ApplicationState) => state.backendConfig);
|
||||
|
||||
const specialLinks = Object.entries(config.specialLinks)
|
||||
.filter(([_, value]) => value !== "")
|
||||
|
|
|
@ -9,8 +9,7 @@ import "./style/index.scss";
|
|||
import {Login} from "../pages/login/login";
|
||||
|
||||
export const Landing: React.FC = () => {
|
||||
return (
|
||||
<Container>
|
||||
return (<Container>
|
||||
<HeaderBar/>
|
||||
<Switch>
|
||||
<Route path="/history">
|
||||
|
@ -24,5 +23,5 @@ export const Landing: React.FC = () => {
|
|||
</Route>
|
||||
</Switch>
|
||||
<Footer/>
|
||||
</Container>);
|
||||
</Container>)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react'
|
||||
import React, {Fragment} from 'react'
|
||||
import {Navbar} from 'react-bootstrap';
|
||||
import {useSelector} from "react-redux";
|
||||
import {ApplicationState} from "../../../../../redux";
|
||||
|
@ -27,19 +27,19 @@ const HeaderBar: React.FC = () => {
|
|||
</div>
|
||||
<div className="d-inline-flex">
|
||||
{user.status === LoginStatus.forbidden ?
|
||||
<>
|
||||
<Fragment>
|
||||
<span className={"mr-1"}>
|
||||
<NewGuestNoteButton/>
|
||||
</span>
|
||||
<SignInButton/>
|
||||
</>
|
||||
</Fragment>
|
||||
:
|
||||
<>
|
||||
<Fragment>
|
||||
<span className={"mr-1"}>
|
||||
<NewUserNoteButton/>
|
||||
</span>
|
||||
<UserDropdown/>
|
||||
</>
|
||||
</Fragment>
|
||||
}
|
||||
</div>
|
||||
</Navbar>
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
import {Dropdown} from "react-bootstrap";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import React from "react";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {useSelector} from "react-redux";
|
||||
import {ApplicationState} from "../../../../../redux";
|
||||
import {LinkContainer} from "react-router-bootstrap";
|
||||
import {clearUser} from "../../../../../redux/user/actions";
|
||||
import {clearUser} from "../../../../../redux/user/methods";
|
||||
import "./user-dropdown.scss";
|
||||
import {Trans} from "react-i18next";
|
||||
|
||||
export const UserDropdown: React.FC = () => {
|
||||
const user = useSelector((state: ApplicationState) => state.user);
|
||||
const dispatch = useDispatch()
|
||||
|
||||
return (
|
||||
<Dropdown alignRight>
|
||||
|
@ -43,7 +42,7 @@ export const UserDropdown: React.FC = () => {
|
|||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
onClick={() => {
|
||||
dispatch(clearUser());
|
||||
clearUser();
|
||||
}}>
|
||||
<FontAwesomeIcon icon="sign-out-alt"/>
|
||||
<Trans i18nKey="signOut"/>
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import {Trans, useTranslation} from "react-i18next";
|
||||
import {Button, Form, Alert} from "react-bootstrap";
|
||||
import {Alert, Button, Form} from "react-bootstrap";
|
||||
import React, {Fragment, useState} from "react";
|
||||
import {postEmailLogin} from "../../../../../api/user";
|
||||
import {useDispatch} from "react-redux";
|
||||
import {getAndSetUser} from "../../../../initialize/initialize-user-state-from-api";
|
||||
import {getAndSetUser} from "../../../../../utils/apiUtils";
|
||||
|
||||
const ViaEMail: React.FC = () => {
|
||||
const {t} = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [error, setError] = useState(false);
|
||||
|
@ -15,10 +13,11 @@ const ViaEMail: React.FC = () => {
|
|||
postEmailLogin(email, password)
|
||||
.then(loginJson => {
|
||||
console.log(loginJson)
|
||||
getAndSetUser(dispatch);
|
||||
getAndSetUser();
|
||||
}).catch(_reason => {
|
||||
setError(true);
|
||||
})
|
||||
}
|
||||
)
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
|
@ -55,8 +54,7 @@ const ViaEMail: React.FC = () => {
|
|||
<Button
|
||||
type="submit"
|
||||
size="sm"
|
||||
variant="primary"
|
||||
>
|
||||
variant="primary">
|
||||
<Trans i18nKey="signIn"/>
|
||||
</Button>
|
||||
</Form>
|
||||
|
|
|
@ -9,7 +9,7 @@ import {ApplicationState} from "../../../../redux";
|
|||
|
||||
const Login: React.FC = () => {
|
||||
useTranslation();
|
||||
const authProviders = useSelector((state: ApplicationState) => state.applicationConfig.authProviders);
|
||||
const authProviders = useSelector((state: ApplicationState) => state.backendConfig.authProviders);
|
||||
|
||||
const emailForm = authProviders.email ? <ViaEMail/> : null
|
||||
const ldapForm = authProviders.ldap ? <ViaLdap/> : null
|
||||
|
|
|
@ -2,27 +2,23 @@ import React from 'react'
|
|||
import ReactDOM from 'react-dom'
|
||||
import {BrowserRouter as Router} from 'react-router-dom'
|
||||
import * as serviceWorker from './service-worker';
|
||||
import {ApplicationStateStoreProvider} from "./redux/application-state-store-provider/application-state-store-provider";
|
||||
import {setUpI18n} from "./initializers/i18n";
|
||||
import {InitializeUserStateFromApi} from "./components/initialize/initialize-user-state-from-api";
|
||||
import {Landing} from "./components/landing/layout";
|
||||
import {setUpFontAwesome} from "./initializers/fontAwesome";
|
||||
import {InitializeConfigStateFromApi} from "./components/initialize/initalize-config-state-from-api";
|
||||
import {ApplicationLoader} from "./components/application-loader/application-loader";
|
||||
import {Provider} from "react-redux";
|
||||
import {store} from "./utils/store";
|
||||
import {setUp} from "./initializers";
|
||||
|
||||
const initTasks = setUp();
|
||||
|
||||
setUpFontAwesome();
|
||||
setUpI18n().then(
|
||||
() => {
|
||||
ReactDOM.render(
|
||||
<ApplicationStateStoreProvider>
|
||||
<InitializeUserStateFromApi/>
|
||||
<InitializeConfigStateFromApi/>
|
||||
<Provider store={store}>
|
||||
<ApplicationLoader initTasks={initTasks}>
|
||||
<Router>
|
||||
<Landing/>
|
||||
</Router>
|
||||
</ApplicationStateStoreProvider>
|
||||
</ApplicationLoader>
|
||||
</Provider>
|
||||
, document.getElementById('root')
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
// If you want your app to work offline and load faster, you can change
|
||||
|
|
24
src/initializers/configLoader.ts
Normal file
24
src/initializers/configLoader.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import {getBackendConfig, getFrontendConfig} from "../api/config";
|
||||
import {setFrontendConfig} from "../redux/frontend-config/methods";
|
||||
import {setBackendConfig} from "../redux/backend-config/methods";
|
||||
import {getAndSetUser} from "../utils/apiUtils";
|
||||
|
||||
export function loadAllConfig() {
|
||||
return getFrontendConfig()
|
||||
.then((frontendConfig) => {
|
||||
if (!frontendConfig) {
|
||||
return Promise.reject("Frontend config empty!");
|
||||
}
|
||||
setFrontendConfig(frontendConfig);
|
||||
return getBackendConfig()
|
||||
})
|
||||
.then((backendConfig) => {
|
||||
if (!backendConfig) {
|
||||
return Promise.reject("Backend config empty!");
|
||||
}
|
||||
setBackendConfig(backendConfig)
|
||||
}).then(() => {
|
||||
getAndSetUser();
|
||||
})
|
||||
}
|
||||
|
8
src/initializers/index.ts
Normal file
8
src/initializers/index.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import {setUpFontAwesome} from "./fontAwesome";
|
||||
import {setUpI18n} from "./i18n";
|
||||
import {loadAllConfig} from "./configLoader";
|
||||
|
||||
export function setUp() {
|
||||
setUpFontAwesome();
|
||||
return [setUpI18n(), loadAllConfig()]
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
import {Action, ActionCreator} from 'redux';
|
||||
import {ApplicationConfigState} from "./types";
|
||||
|
||||
export const SET_APPLICATION_CONFIG_ACTION_TYPE = 'config/set';
|
||||
|
||||
export interface SetApplicationConfigAction extends Action {
|
||||
type: string;
|
||||
payload: {
|
||||
state: ApplicationConfigState;
|
||||
};
|
||||
}
|
||||
|
||||
export const setApplicationConfig: ActionCreator<SetApplicationConfigAction> = (state: ApplicationConfigState) => ({
|
||||
type: SET_APPLICATION_CONFIG_ACTION_TYPE,
|
||||
payload: {
|
||||
state
|
||||
},
|
||||
})
|
||||
|
||||
export type ApplicationConfigActions = SetApplicationConfigAction;
|
|
@ -1,13 +0,0 @@
|
|||
import {createStore} from 'redux';
|
||||
import {allReducers} from '../index';
|
||||
import {Provider} from "react-redux";
|
||||
import React, {ReactNode} from "react";
|
||||
|
||||
interface JustChildrenProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const ApplicationStateStoreProvider: React.FC<JustChildrenProps> = (props: JustChildrenProps) => {
|
||||
const store = createStore(allReducers);
|
||||
return <Provider store={store}>{props.children}</Provider>;
|
||||
}
|
12
src/redux/backend-config/methods.ts
Normal file
12
src/redux/backend-config/methods.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import {BackendConfigState, SET_BACKEND_CONFIG_ACTION_TYPE, SetBackendConfigAction} from "./types";
|
||||
import {store} from "../../utils/store";
|
||||
|
||||
export const setBackendConfig = (state: BackendConfigState) => {
|
||||
const action: SetBackendConfigAction = {
|
||||
type: SET_BACKEND_CONFIG_ACTION_TYPE,
|
||||
payload: {
|
||||
state
|
||||
}
|
||||
};
|
||||
store.dispatch(action)
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
import {Reducer} from 'redux';
|
||||
import {ApplicationConfigState} from './types';
|
||||
import {ApplicationConfigActions, SET_APPLICATION_CONFIG_ACTION_TYPE} from "./actions";
|
||||
import {BackendConfigActions, BackendConfigState, SET_BACKEND_CONFIG_ACTION_TYPE} from './types';
|
||||
|
||||
export const initialState: ApplicationConfigState = {
|
||||
export const initialState: BackendConfigState = {
|
||||
allowAnonymous: true,
|
||||
authProviders: {
|
||||
facebook: true,
|
||||
|
@ -23,9 +22,9 @@ export const initialState: ApplicationConfigState = {
|
|||
}
|
||||
};
|
||||
|
||||
export const ApplicationConfigReducer: Reducer<ApplicationConfigState, ApplicationConfigActions> = (state: ApplicationConfigState = initialState, action: ApplicationConfigActions) => {
|
||||
export const BackendConfigReducer: Reducer<BackendConfigState, BackendConfigActions> = (state: BackendConfigState = initialState, action: BackendConfigActions) => {
|
||||
switch (action.type) {
|
||||
case SET_APPLICATION_CONFIG_ACTION_TYPE:
|
||||
case SET_BACKEND_CONFIG_ACTION_TYPE:
|
||||
return action.payload.state;
|
||||
default:
|
||||
return state;
|
|
@ -1,10 +1,13 @@
|
|||
export interface ApplicationConfigState {
|
||||
import {Action} from "redux";
|
||||
|
||||
export const SET_BACKEND_CONFIG_ACTION_TYPE = 'backend-config/set';
|
||||
|
||||
export interface BackendConfigState {
|
||||
allowAnonymous: boolean,
|
||||
authProviders: AuthProvidersState,
|
||||
specialLinks: SpecialLinks,
|
||||
}
|
||||
|
||||
|
||||
export interface AuthProvidersState {
|
||||
facebook: true,
|
||||
github: false,
|
||||
|
@ -23,3 +26,12 @@ export interface SpecialLinks {
|
|||
termsOfUse: string,
|
||||
imprint: string,
|
||||
}
|
||||
|
||||
export interface SetBackendConfigAction extends Action {
|
||||
type: string;
|
||||
payload: {
|
||||
state: BackendConfigState;
|
||||
};
|
||||
}
|
||||
|
||||
export type BackendConfigActions = SetBackendConfigAction;
|
12
src/redux/frontend-config/methods.ts
Normal file
12
src/redux/frontend-config/methods.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import {FrontendConfigState, SET_FRONTEND_CONFIG_ACTION_TYPE, SetFrontendConfigAction} from "./types";
|
||||
import {store} from "../../utils/store";
|
||||
|
||||
export const setFrontendConfig = (state: FrontendConfigState) => {
|
||||
const action: SetFrontendConfigAction = {
|
||||
type: SET_FRONTEND_CONFIG_ACTION_TYPE,
|
||||
payload: {
|
||||
state
|
||||
}
|
||||
}
|
||||
store.dispatch(action);
|
||||
}
|
15
src/redux/frontend-config/reducers.ts
Normal file
15
src/redux/frontend-config/reducers.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import {Reducer} from 'redux';
|
||||
import {FrontendConfigActions, FrontendConfigState, SET_FRONTEND_CONFIG_ACTION_TYPE} from './types';
|
||||
|
||||
export const initialState: FrontendConfigState = {
|
||||
backendUrl: ""
|
||||
};
|
||||
|
||||
export const FrontendConfigReducer: Reducer<FrontendConfigState, FrontendConfigActions> = (state: FrontendConfigState = initialState, action: FrontendConfigActions) => {
|
||||
switch (action.type) {
|
||||
case SET_FRONTEND_CONFIG_ACTION_TYPE:
|
||||
return action.payload.state;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
16
src/redux/frontend-config/types.ts
Normal file
16
src/redux/frontend-config/types.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import {Action} from "redux";
|
||||
|
||||
export const SET_FRONTEND_CONFIG_ACTION_TYPE = 'frontend-config/set';
|
||||
|
||||
export interface SetFrontendConfigAction extends Action {
|
||||
type: string;
|
||||
payload: {
|
||||
state: FrontendConfigState;
|
||||
};
|
||||
}
|
||||
|
||||
export interface FrontendConfigState {
|
||||
backendUrl: string,
|
||||
}
|
||||
|
||||
export type FrontendConfigActions = SetFrontendConfigAction;
|
|
@ -1,19 +1,23 @@
|
|||
import {combineReducers, Reducer} from 'redux';
|
||||
import {UserState} from "./user/types";
|
||||
import {UserReducer} from "./user/reducers";
|
||||
import {ApplicationConfigReducer} from "./application-config/reducers";
|
||||
import {ApplicationConfigState} from "./application-config/types";
|
||||
import {ModalShowReducer} from "./modal/reducers";
|
||||
import {ModalShowState} from "./modal/types";
|
||||
import {BackendConfigState} from "./backend-config/types";
|
||||
import {FrontendConfigState} from "./frontend-config/types";
|
||||
import {BackendConfigReducer} from "./backend-config/reducers";
|
||||
import {FrontendConfigReducer} from "./frontend-config/reducers";
|
||||
|
||||
export interface ApplicationState {
|
||||
user: UserState;
|
||||
modalShow: ModalShowState;
|
||||
applicationConfig: ApplicationConfigState;
|
||||
backendConfig: BackendConfigState;
|
||||
frontendConfig: FrontendConfigState;
|
||||
}
|
||||
|
||||
export const allReducers: Reducer<ApplicationState> = combineReducers<ApplicationState>({
|
||||
user: UserReducer,
|
||||
modalShow: ModalShowReducer,
|
||||
applicationConfig: ApplicationConfigReducer
|
||||
backendConfig: BackendConfigReducer,
|
||||
frontendConfig: FrontendConfigReducer
|
||||
});
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
import {Action, ActionCreator} from 'redux';
|
||||
|
||||
export const SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE = 'modal/history-delete/set';
|
||||
|
||||
export interface SetHistoryDeleteModalShowAction extends Action {
|
||||
type: string;
|
||||
payload: boolean;
|
||||
}
|
||||
|
||||
export const setSignInModalShow: ActionCreator<SetHistoryDeleteModalShowAction> = (historyDelete: boolean) => ({
|
||||
type: SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE,
|
||||
payload: historyDelete,
|
||||
})
|
||||
|
||||
export type ModalShowActions = SetHistoryDeleteModalShowAction;
|
7
src/redux/modal/methods.ts
Normal file
7
src/redux/modal/methods.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import {ActionCreator} from "redux";
|
||||
import {SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE, SetHistoryDeleteModalShowAction} from "./types";
|
||||
|
||||
export const setSignInModalShow: ActionCreator<SetHistoryDeleteModalShowAction> = (historyDelete: boolean) => ({
|
||||
type: SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE,
|
||||
payload: historyDelete,
|
||||
})
|
|
@ -1,6 +1,5 @@
|
|||
import {Reducer} from 'redux';
|
||||
import {ModalShowState} from './types';
|
||||
import {ModalShowActions, SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE} from "./actions";
|
||||
import {ModalShowActions, ModalShowState, SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE} from './types';
|
||||
|
||||
export const initialState: ModalShowState = {
|
||||
historyDelete: false
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
import {Action} from "redux";
|
||||
|
||||
export const SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE = 'modal/history-delete/set';
|
||||
|
||||
export interface ModalShowState {
|
||||
historyDelete: boolean
|
||||
}
|
||||
|
||||
export interface SetHistoryDeleteModalShowAction extends Action {
|
||||
type: string;
|
||||
payload: boolean;
|
||||
}
|
||||
|
||||
export type ModalShowActions = SetHistoryDeleteModalShowAction;
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
import {Action, ActionCreator} from 'redux';
|
||||
import {UserState} from "./types";
|
||||
|
||||
export const SET_USER_ACTION_TYPE = 'user/set';
|
||||
export const CLEAR_USER_ACTION_TYPE = 'user/clear';
|
||||
|
||||
export interface SetUserAction extends Action {
|
||||
type: string;
|
||||
payload: {
|
||||
state: UserState,
|
||||
};
|
||||
}
|
||||
|
||||
export interface ClearUserAction extends Action {
|
||||
type: string;
|
||||
payload: {};
|
||||
}
|
||||
|
||||
export const setUser: ActionCreator<SetUserAction> = (state: UserState) => ({
|
||||
type: SET_USER_ACTION_TYPE,
|
||||
payload: {
|
||||
state
|
||||
},
|
||||
})
|
||||
|
||||
export const clearUser: ActionCreator<ClearUserAction> = () => ({
|
||||
type: CLEAR_USER_ACTION_TYPE,
|
||||
payload: {},
|
||||
})
|
||||
|
||||
|
||||
export type UserActions = SetUserAction | ClearUserAction;
|
20
src/redux/user/methods.ts
Normal file
20
src/redux/user/methods.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import {CLEAR_USER_ACTION_TYPE, ClearUserAction, SET_USER_ACTION_TYPE, SetUserAction, UserState} from "./types";
|
||||
import {store} from "../../utils/store";
|
||||
|
||||
export const setUser = (state: UserState) => {
|
||||
const action: SetUserAction = {
|
||||
type: SET_USER_ACTION_TYPE,
|
||||
payload: {
|
||||
state
|
||||
}
|
||||
}
|
||||
store.dispatch(action);
|
||||
}
|
||||
|
||||
export const clearUser = () => {
|
||||
const action: ClearUserAction = {
|
||||
type: CLEAR_USER_ACTION_TYPE,
|
||||
payload: {},
|
||||
}
|
||||
store.dispatch(action);
|
||||
}
|
|
@ -1,6 +1,12 @@
|
|||
import {Reducer} from 'redux';
|
||||
import {LoginStatus, UserState} from './types';
|
||||
import {CLEAR_USER_ACTION_TYPE, SET_USER_ACTION_TYPE, SetUserAction, UserActions} from "./actions";
|
||||
import {
|
||||
CLEAR_USER_ACTION_TYPE,
|
||||
LoginStatus,
|
||||
SET_USER_ACTION_TYPE,
|
||||
SetUserAction,
|
||||
UserActions,
|
||||
UserState
|
||||
} from './types';
|
||||
|
||||
export const initialState: UserState = {
|
||||
id: "",
|
||||
|
|
|
@ -1,3 +1,20 @@
|
|||
import {Action} from "redux";
|
||||
|
||||
export const SET_USER_ACTION_TYPE = 'user/set';
|
||||
export const CLEAR_USER_ACTION_TYPE = 'user/clear';
|
||||
|
||||
export interface SetUserAction extends Action {
|
||||
type: string;
|
||||
payload: {
|
||||
state: UserState,
|
||||
};
|
||||
}
|
||||
|
||||
export interface ClearUserAction extends Action {
|
||||
type: string;
|
||||
payload: {};
|
||||
}
|
||||
|
||||
export interface UserState {
|
||||
status: LoginStatus;
|
||||
id: string;
|
||||
|
@ -9,3 +26,5 @@ export enum LoginStatus {
|
|||
forbidden = "forbidden",
|
||||
ok = "ok"
|
||||
}
|
||||
|
||||
export type UserActions = SetUserAction | ClearUserAction;
|
||||
|
|
34
src/utils/apiUtils.ts
Normal file
34
src/utils/apiUtils.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import {getMe} from "../api/user";
|
||||
import {LoginStatus} from "../redux/user/types";
|
||||
import {setUser} from "../redux/user/methods";
|
||||
import {store} from "./store";
|
||||
|
||||
export const getAndSetUser = () => {
|
||||
getMe()
|
||||
.then(expectResponseCode())
|
||||
.then(user => {
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
setUser({
|
||||
status: LoginStatus.ok,
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
photo: user.photo,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export const getBackendUrl = () => {
|
||||
return store.getState().frontendConfig.backendUrl;
|
||||
}
|
||||
|
||||
export const expectResponseCode = (code: number = 200): ((response: Response) => Promise<any>) => {
|
||||
return (response: Response) => {
|
||||
if (response.status !== code) {
|
||||
return Promise.reject(`Response code not ${code}`);
|
||||
} else {
|
||||
return Promise.resolve(response);
|
||||
}
|
||||
}
|
||||
}
|
4
src/utils/store.ts
Normal file
4
src/utils/store.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
import {createStore} from "redux";
|
||||
import {allReducers} from "../redux";
|
||||
|
||||
export const store = createStore(allReducers);
|
Loading…
Reference in a new issue