mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-22 01:36:29 -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 () => {
|
import {FrontendConfigState} from "../redux/frontend-config/types";
|
||||||
return fetch('/config');
|
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,27 +1,25 @@
|
||||||
|
import {expectResponseCode, getBackendUrl} from "../utils/apiUtils";
|
||||||
|
|
||||||
export const getMe = async () => {
|
export const getMe = async () => {
|
||||||
return fetch('/me');
|
return fetch('/me');
|
||||||
}
|
}
|
||||||
|
|
||||||
export const postEmailLogin = async (email: string, password: string) => {
|
export const postEmailLogin = async (email: string, password: string) => {
|
||||||
return fetch("/login", {
|
return fetch(getBackendUrl() + "/login", {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
cache: 'no-cache',
|
cache: 'no-cache',
|
||||||
credentials: 'same-origin',
|
credentials: 'same-origin',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
redirect: 'follow',
|
redirect: 'follow',
|
||||||
referrerPolicy: 'no-referrer',
|
referrerPolicy: 'no-referrer',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
email: email,
|
email: email,
|
||||||
password: password,
|
password: password,
|
||||||
})
|
})
|
||||||
}).then(response => {
|
})
|
||||||
if (response.status !== 200) {
|
.then(expectResponseCode())
|
||||||
return Promise.reject("Response code not 200");
|
.then(response => response.json());
|
||||||
} else {
|
|
||||||
return 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)
|
const specialLinks = Object.entries(config.specialLinks)
|
||||||
.filter(([_, value]) => value !== "")
|
.filter(([_, value]) => value !== "")
|
||||||
|
|
|
@ -9,20 +9,19 @@ import "./style/index.scss";
|
||||||
import {Login} from "../pages/login/login";
|
import {Login} from "../pages/login/login";
|
||||||
|
|
||||||
export const Landing: React.FC = () => {
|
export const Landing: React.FC = () => {
|
||||||
return (
|
return (<Container>
|
||||||
<Container>
|
<HeaderBar/>
|
||||||
<HeaderBar/>
|
<Switch>
|
||||||
<Switch>
|
<Route path="/history">
|
||||||
<Route path="/history">
|
<History/>
|
||||||
<History/>
|
</Route>
|
||||||
</Route>
|
<Route path="/intro">
|
||||||
<Route path="/intro">
|
<Intro/>
|
||||||
<Intro/>
|
</Route>
|
||||||
</Route>
|
<Route path="/login">
|
||||||
<Route path="/login">
|
<Login/>
|
||||||
<Login/>
|
</Route>
|
||||||
</Route>
|
</Switch>
|
||||||
</Switch>
|
<Footer/>
|
||||||
<Footer/>
|
</Container>)
|
||||||
</Container>);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react'
|
import React, {Fragment} from 'react'
|
||||||
import {Navbar} from 'react-bootstrap';
|
import {Navbar} from 'react-bootstrap';
|
||||||
import {useSelector} from "react-redux";
|
import {useSelector} from "react-redux";
|
||||||
import {ApplicationState} from "../../../../../redux";
|
import {ApplicationState} from "../../../../../redux";
|
||||||
|
@ -27,19 +27,19 @@ const HeaderBar: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
<div className="d-inline-flex">
|
<div className="d-inline-flex">
|
||||||
{user.status === LoginStatus.forbidden ?
|
{user.status === LoginStatus.forbidden ?
|
||||||
<>
|
<Fragment>
|
||||||
<span className={"mr-1"}>
|
<span className={"mr-1"}>
|
||||||
<NewGuestNoteButton/>
|
<NewGuestNoteButton/>
|
||||||
</span>
|
</span>
|
||||||
<SignInButton/>
|
<SignInButton/>
|
||||||
</>
|
</Fragment>
|
||||||
:
|
:
|
||||||
<>
|
<Fragment>
|
||||||
<span className={"mr-1"}>
|
<span className={"mr-1"}>
|
||||||
<NewUserNoteButton/>
|
<NewUserNoteButton/>
|
||||||
</span>
|
</span>
|
||||||
<UserDropdown/>
|
<UserDropdown/>
|
||||||
</>
|
</Fragment>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
import {Dropdown} from "react-bootstrap";
|
import {Dropdown} from "react-bootstrap";
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {useDispatch, useSelector} from "react-redux";
|
import {useSelector} from "react-redux";
|
||||||
import {ApplicationState} from "../../../../../redux";
|
import {ApplicationState} from "../../../../../redux";
|
||||||
import {LinkContainer} from "react-router-bootstrap";
|
import {LinkContainer} from "react-router-bootstrap";
|
||||||
import {clearUser} from "../../../../../redux/user/actions";
|
import {clearUser} from "../../../../../redux/user/methods";
|
||||||
import "./user-dropdown.scss";
|
import "./user-dropdown.scss";
|
||||||
import {Trans} from "react-i18next";
|
import {Trans} from "react-i18next";
|
||||||
|
|
||||||
export const UserDropdown: React.FC = () => {
|
export const UserDropdown: React.FC = () => {
|
||||||
const user = useSelector((state: ApplicationState) => state.user);
|
const user = useSelector((state: ApplicationState) => state.user);
|
||||||
const dispatch = useDispatch()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown alignRight>
|
<Dropdown alignRight>
|
||||||
|
@ -43,7 +42,7 @@ export const UserDropdown: React.FC = () => {
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
<Dropdown.Item
|
<Dropdown.Item
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
dispatch(clearUser());
|
clearUser();
|
||||||
}}>
|
}}>
|
||||||
<FontAwesomeIcon icon="sign-out-alt"/>
|
<FontAwesomeIcon icon="sign-out-alt"/>
|
||||||
<Trans i18nKey="signOut"/>
|
<Trans i18nKey="signOut"/>
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import {Trans, useTranslation} from "react-i18next";
|
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 React, {Fragment, useState} from "react";
|
||||||
import {postEmailLogin} from "../../../../../api/user";
|
import {postEmailLogin} from "../../../../../api/user";
|
||||||
import {useDispatch} from "react-redux";
|
import {getAndSetUser} from "../../../../../utils/apiUtils";
|
||||||
import {getAndSetUser} from "../../../../initialize/initialize-user-state-from-api";
|
|
||||||
|
|
||||||
const ViaEMail: React.FC = () => {
|
const ViaEMail: React.FC = () => {
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
const dispatch = useDispatch();
|
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
|
@ -15,10 +13,11 @@ const ViaEMail: React.FC = () => {
|
||||||
postEmailLogin(email, password)
|
postEmailLogin(email, password)
|
||||||
.then(loginJson => {
|
.then(loginJson => {
|
||||||
console.log(loginJson)
|
console.log(loginJson)
|
||||||
getAndSetUser(dispatch);
|
getAndSetUser();
|
||||||
}).catch(_reason => {
|
}).catch(_reason => {
|
||||||
setError(true);
|
setError(true);
|
||||||
})
|
}
|
||||||
|
)
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,8 +54,7 @@ const ViaEMail: React.FC = () => {
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="primary"
|
variant="primary">
|
||||||
>
|
|
||||||
<Trans i18nKey="signIn"/>
|
<Trans i18nKey="signIn"/>
|
||||||
</Button>
|
</Button>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -64,4 +62,4 @@ const ViaEMail: React.FC = () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { ViaEMail }
|
export {ViaEMail}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {ApplicationState} from "../../../../redux";
|
||||||
|
|
||||||
const Login: React.FC = () => {
|
const Login: React.FC = () => {
|
||||||
useTranslation();
|
useTranslation();
|
||||||
const authProviders = useSelector((state: ApplicationState) => state.applicationConfig.authProviders);
|
const authProviders = useSelector((state: ApplicationState) => state.backendConfig.authProviders);
|
||||||
|
|
||||||
const emailForm = authProviders.email ? <ViaEMail/> : null
|
const emailForm = authProviders.email ? <ViaEMail/> : null
|
||||||
const ldapForm = authProviders.ldap ? <ViaLdap/> : null
|
const ldapForm = authProviders.ldap ? <ViaLdap/> : null
|
||||||
|
|
|
@ -2,27 +2,23 @@ import React from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import {BrowserRouter as Router} from 'react-router-dom'
|
import {BrowserRouter as Router} from 'react-router-dom'
|
||||||
import * as serviceWorker from './service-worker';
|
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 {Landing} from "./components/landing/layout";
|
||||||
import {setUpFontAwesome} from "./initializers/fontAwesome";
|
import {ApplicationLoader} from "./components/application-loader/application-loader";
|
||||||
import {InitializeConfigStateFromApi} from "./components/initialize/initalize-config-state-from-api";
|
import {Provider} from "react-redux";
|
||||||
|
import {store} from "./utils/store";
|
||||||
|
import {setUp} from "./initializers";
|
||||||
|
|
||||||
setUpFontAwesome();
|
const initTasks = setUp();
|
||||||
setUpI18n().then(
|
|
||||||
() => {
|
ReactDOM.render(
|
||||||
ReactDOM.render(
|
<Provider store={store}>
|
||||||
<ApplicationStateStoreProvider>
|
<ApplicationLoader initTasks={initTasks}>
|
||||||
<InitializeUserStateFromApi/>
|
<Router>
|
||||||
<InitializeConfigStateFromApi/>
|
<Landing/>
|
||||||
<Router>
|
</Router>
|
||||||
<Landing/>
|
</ApplicationLoader>
|
||||||
</Router>
|
</Provider>
|
||||||
</ApplicationStateStoreProvider>
|
, document.getElementById('root')
|
||||||
, document.getElementById('root')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// If you want your app to work offline and load faster, you can change
|
// 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 {Reducer} from 'redux';
|
||||||
import {ApplicationConfigState} from './types';
|
import {BackendConfigActions, BackendConfigState, SET_BACKEND_CONFIG_ACTION_TYPE} from './types';
|
||||||
import {ApplicationConfigActions, SET_APPLICATION_CONFIG_ACTION_TYPE} from "./actions";
|
|
||||||
|
|
||||||
export const initialState: ApplicationConfigState = {
|
export const initialState: BackendConfigState = {
|
||||||
allowAnonymous: true,
|
allowAnonymous: true,
|
||||||
authProviders: {
|
authProviders: {
|
||||||
facebook: true,
|
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) {
|
switch (action.type) {
|
||||||
case SET_APPLICATION_CONFIG_ACTION_TYPE:
|
case SET_BACKEND_CONFIG_ACTION_TYPE:
|
||||||
return action.payload.state;
|
return action.payload.state;
|
||||||
default:
|
default:
|
||||||
return state;
|
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,
|
allowAnonymous: boolean,
|
||||||
authProviders: AuthProvidersState,
|
authProviders: AuthProvidersState,
|
||||||
specialLinks: SpecialLinks,
|
specialLinks: SpecialLinks,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface AuthProvidersState {
|
export interface AuthProvidersState {
|
||||||
facebook: true,
|
facebook: true,
|
||||||
github: false,
|
github: false,
|
||||||
|
@ -23,3 +26,12 @@ export interface SpecialLinks {
|
||||||
termsOfUse: string,
|
termsOfUse: string,
|
||||||
imprint: 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 {combineReducers, Reducer} from 'redux';
|
||||||
import {UserState} from "./user/types";
|
import {UserState} from "./user/types";
|
||||||
import {UserReducer} from "./user/reducers";
|
import {UserReducer} from "./user/reducers";
|
||||||
import {ApplicationConfigReducer} from "./application-config/reducers";
|
|
||||||
import {ApplicationConfigState} from "./application-config/types";
|
|
||||||
import {ModalShowReducer} from "./modal/reducers";
|
import {ModalShowReducer} from "./modal/reducers";
|
||||||
import {ModalShowState} from "./modal/types";
|
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 {
|
export interface ApplicationState {
|
||||||
user: UserState;
|
user: UserState;
|
||||||
modalShow: ModalShowState;
|
modalShow: ModalShowState;
|
||||||
applicationConfig: ApplicationConfigState;
|
backendConfig: BackendConfigState;
|
||||||
|
frontendConfig: FrontendConfigState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const allReducers: Reducer<ApplicationState> = combineReducers<ApplicationState>({
|
export const allReducers: Reducer<ApplicationState> = combineReducers<ApplicationState>({
|
||||||
user: UserReducer,
|
user: UserReducer,
|
||||||
modalShow: ModalShowReducer,
|
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 {Reducer} from 'redux';
|
||||||
import {ModalShowState} from './types';
|
import {ModalShowActions, ModalShowState, SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE} from './types';
|
||||||
import {ModalShowActions, SET_HISTORY_DELETE_MODAL_SHOW_ACTION_TYPE} from "./actions";
|
|
||||||
|
|
||||||
export const initialState: ModalShowState = {
|
export const initialState: ModalShowState = {
|
||||||
historyDelete: false
|
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 {
|
export interface ModalShowState {
|
||||||
historyDelete: boolean
|
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 {Reducer} from 'redux';
|
||||||
import {LoginStatus, UserState} from './types';
|
import {
|
||||||
import {CLEAR_USER_ACTION_TYPE, SET_USER_ACTION_TYPE, SetUserAction, UserActions} from "./actions";
|
CLEAR_USER_ACTION_TYPE,
|
||||||
|
LoginStatus,
|
||||||
|
SET_USER_ACTION_TYPE,
|
||||||
|
SetUserAction,
|
||||||
|
UserActions,
|
||||||
|
UserState
|
||||||
|
} from './types';
|
||||||
|
|
||||||
export const initialState: UserState = {
|
export const initialState: UserState = {
|
||||||
id: "",
|
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 {
|
export interface UserState {
|
||||||
status: LoginStatus;
|
status: LoginStatus;
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -9,3 +26,5 @@ export enum LoginStatus {
|
||||||
forbidden = "forbidden",
|
forbidden = "forbidden",
|
||||||
ok = "ok"
|
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