mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-19 01:35:18 -04: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
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,20 +9,19 @@ import "./style/index.scss";
|
|||
import {Login} from "../pages/login/login";
|
||||
|
||||
export const Landing: React.FC = () => {
|
||||
return (
|
||||
<Container>
|
||||
<HeaderBar/>
|
||||
<Switch>
|
||||
<Route path="/history">
|
||||
<History/>
|
||||
</Route>
|
||||
<Route path="/intro">
|
||||
<Intro/>
|
||||
</Route>
|
||||
<Route path="/login">
|
||||
<Login/>
|
||||
</Route>
|
||||
</Switch>
|
||||
<Footer/>
|
||||
</Container>);
|
||||
return (<Container>
|
||||
<HeaderBar/>
|
||||
<Switch>
|
||||
<Route path="/history">
|
||||
<History/>
|
||||
</Route>
|
||||
<Route path="/intro">
|
||||
<Intro/>
|
||||
</Route>
|
||||
<Route path="/login">
|
||||
<Login/>
|
||||
</Route>
|
||||
</Switch>
|
||||
<Footer/>
|
||||
</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);
|
||||
}).catch(_reason => {
|
||||
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>
|
||||
|
@ -64,4 +62,4 @@ const ViaEMail: React.FC = () => {
|
|||
);
|
||||
}
|
||||
|
||||
export { ViaEMail }
|
||||
export {ViaEMail}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue