diff --git a/public/config b/public/backend-config.json similarity index 100% rename from public/config rename to public/backend-config.json diff --git a/public/config.json b/public/config.json new file mode 100644 index 000000000..c2b272d3b --- /dev/null +++ b/public/config.json @@ -0,0 +1,3 @@ +{ + "backendUrl": "http://localhost:3000" +} diff --git a/src/api/config.ts b/src/api/config.ts index 74e29042b..7935873ad 100644 --- a/src/api/config.ts +++ b/src/api/config.ts @@ -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>); +} \ No newline at end of file diff --git a/src/api/user.ts b/src/api/user.ts index 29ccc4485..ce68da5d9 100644 --- a/src/api/user.ts +++ b/src/api/user.ts @@ -1,27 +1,25 @@ +import {expectResponseCode, getBackendUrl} from "../utils/apiUtils"; + export const getMe = async () => { - return fetch('/me'); + return fetch('/me'); } export const postEmailLogin = async (email: string, password: string) => { - return fetch("/login", { - method: 'POST', - mode: 'cors', - cache: 'no-cache', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json' - }, - redirect: 'follow', - referrerPolicy: 'no-referrer', - body: JSON.stringify({ - email: email, - password: password, - }) - }).then(response => { - if (response.status !== 200) { - return Promise.reject("Response code not 200"); - } else { - return response.json(); - } - }) -} + return fetch(getBackendUrl() + "/login", { + method: 'POST', + mode: 'cors', + cache: 'no-cache', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json' + }, + redirect: 'follow', + referrerPolicy: 'no-referrer', + body: JSON.stringify({ + email: email, + password: password, + }) + }) + .then(expectResponseCode()) + .then(response => response.json()); +} \ No newline at end of file diff --git a/src/components/application-loader/application-loader.scss b/src/components/application-loader/application-loader.scss new file mode 100644 index 000000000..744179ce0 --- /dev/null +++ b/src/components/application-loader/application-loader.scss @@ -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); + } + } +} \ No newline at end of file diff --git a/src/components/application-loader/application-loader.tsx b/src/components/application-loader/application-loader.tsx new file mode 100644 index 000000000..ba5dace2f --- /dev/null +++ b/src/components/application-loader/application-loader.tsx @@ -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>); +} diff --git a/src/components/initialize/initalize-config-state-from-api.tsx b/src/components/initialize/initalize-config-state-from-api.tsx deleted file mode 100644 index e27c8e0cd..000000000 --- a/src/components/initialize/initalize-config-state-from-api.tsx +++ /dev/null @@ -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 } diff --git a/src/components/initialize/initialize-user-state-from-api.tsx b/src/components/initialize/initialize-user-state-from-api.tsx deleted file mode 100644 index 6cd1a1c61..000000000 --- a/src/components/initialize/initialize-user-state-from-api.tsx +++ /dev/null @@ -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 } diff --git a/src/components/landing/layout/footer/powered-by-links.tsx b/src/components/landing/layout/footer/powered-by-links.tsx index 0c733c9f9..07a48c9ad 100644 --- a/src/components/landing/layout/footer/powered-by-links.tsx +++ b/src/components/landing/layout/footer/powered-by-links.tsx @@ -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 !== "") diff --git a/src/components/landing/layout/index.tsx b/src/components/landing/layout/index.tsx index c09874eaa..716c90133 100644 --- a/src/components/landing/layout/index.tsx +++ b/src/components/landing/layout/index.tsx @@ -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>) } diff --git a/src/components/landing/layout/navigation/header-bar/header-bar.tsx b/src/components/landing/layout/navigation/header-bar/header-bar.tsx index bd06e78d4..e3ae70a4e 100644 --- a/src/components/landing/layout/navigation/header-bar/header-bar.tsx +++ b/src/components/landing/layout/navigation/header-bar/header-bar.tsx @@ -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> diff --git a/src/components/landing/layout/navigation/user-dropdown/user-dropdown.tsx b/src/components/landing/layout/navigation/user-dropdown/user-dropdown.tsx index a379f1bbd..65f62dde9 100644 --- a/src/components/landing/layout/navigation/user-dropdown/user-dropdown.tsx +++ b/src/components/landing/layout/navigation/user-dropdown/user-dropdown.tsx @@ -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"/> diff --git a/src/components/landing/pages/login/auth/via-email.tsx b/src/components/landing/pages/login/auth/via-email.tsx index 0e232c4b0..f7246e151 100644 --- a/src/components/landing/pages/login/auth/via-email.tsx +++ b/src/components/landing/pages/login/auth/via-email.tsx @@ -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} diff --git a/src/components/landing/pages/login/login.tsx b/src/components/landing/pages/login/login.tsx index fcbb0502f..78a610a88 100644 --- a/src/components/landing/pages/login/login.tsx +++ b/src/components/landing/pages/login/login.tsx @@ -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 diff --git a/src/index.tsx b/src/index.tsx index cd8ff4502..edd130d42 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -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"; -setUpFontAwesome(); -setUpI18n().then( - () => { - ReactDOM.render( - <ApplicationStateStoreProvider> - <InitializeUserStateFromApi/> - <InitializeConfigStateFromApi/> - <Router> - <Landing/> - </Router> - </ApplicationStateStoreProvider> - , document.getElementById('root') - ) - } +const initTasks = setUp(); + +ReactDOM.render( + <Provider store={store}> + <ApplicationLoader initTasks={initTasks}> + <Router> + <Landing/> + </Router> + </ApplicationLoader> + </Provider> + , document.getElementById('root') ); // If you want your app to work offline and load faster, you can change diff --git a/src/initializers/configLoader.ts b/src/initializers/configLoader.ts new file mode 100644 index 000000000..90e5ada01 --- /dev/null +++ b/src/initializers/configLoader.ts @@ -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(); + }) +} + diff --git a/src/initializers/index.ts b/src/initializers/index.ts new file mode 100644 index 000000000..23453831e --- /dev/null +++ b/src/initializers/index.ts @@ -0,0 +1,8 @@ +import {setUpFontAwesome} from "./fontAwesome"; +import {setUpI18n} from "./i18n"; +import {loadAllConfig} from "./configLoader"; + +export function setUp() { + setUpFontAwesome(); + return [setUpI18n(), loadAllConfig()] +} \ No newline at end of file diff --git a/src/redux/application-config/actions.ts b/src/redux/application-config/actions.ts deleted file mode 100644 index ef59c51ae..000000000 --- a/src/redux/application-config/actions.ts +++ /dev/null @@ -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; diff --git a/src/redux/application-state-store-provider/application-state-store-provider.tsx b/src/redux/application-state-store-provider/application-state-store-provider.tsx deleted file mode 100644 index 9a93abb7d..000000000 --- a/src/redux/application-state-store-provider/application-state-store-provider.tsx +++ /dev/null @@ -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>; -} \ No newline at end of file diff --git a/src/redux/backend-config/methods.ts b/src/redux/backend-config/methods.ts new file mode 100644 index 000000000..1b5b304f3 --- /dev/null +++ b/src/redux/backend-config/methods.ts @@ -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) +} diff --git a/src/redux/application-config/reducers.ts b/src/redux/backend-config/reducers.ts similarity index 55% rename from src/redux/application-config/reducers.ts rename to src/redux/backend-config/reducers.ts index 58c8c2dc7..03b1e6746 100644 --- a/src/redux/application-config/reducers.ts +++ b/src/redux/backend-config/reducers.ts @@ -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; diff --git a/src/redux/application-config/types.ts b/src/redux/backend-config/types.ts similarity index 56% rename from src/redux/application-config/types.ts rename to src/redux/backend-config/types.ts index 0e2637a74..43b3423d3 100644 --- a/src/redux/application-config/types.ts +++ b/src/redux/backend-config/types.ts @@ -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; diff --git a/src/redux/frontend-config/methods.ts b/src/redux/frontend-config/methods.ts new file mode 100644 index 000000000..4d07612fa --- /dev/null +++ b/src/redux/frontend-config/methods.ts @@ -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); +} diff --git a/src/redux/frontend-config/reducers.ts b/src/redux/frontend-config/reducers.ts new file mode 100644 index 000000000..3f0ca8a55 --- /dev/null +++ b/src/redux/frontend-config/reducers.ts @@ -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; + } +}; diff --git a/src/redux/frontend-config/types.ts b/src/redux/frontend-config/types.ts new file mode 100644 index 000000000..06175fd42 --- /dev/null +++ b/src/redux/frontend-config/types.ts @@ -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; diff --git a/src/redux/index.ts b/src/redux/index.ts index 9d4bd4fc0..4f07b8c06 100644 --- a/src/redux/index.ts +++ b/src/redux/index.ts @@ -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 }); diff --git a/src/redux/modal/actions.ts b/src/redux/modal/actions.ts deleted file mode 100644 index 470c6d733..000000000 --- a/src/redux/modal/actions.ts +++ /dev/null @@ -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; diff --git a/src/redux/modal/methods.ts b/src/redux/modal/methods.ts new file mode 100644 index 000000000..c9e46a1b8 --- /dev/null +++ b/src/redux/modal/methods.ts @@ -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, +}) diff --git a/src/redux/modal/reducers.ts b/src/redux/modal/reducers.ts index f71669fb3..b529f377c 100644 --- a/src/redux/modal/reducers.ts +++ b/src/redux/modal/reducers.ts @@ -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 diff --git a/src/redux/modal/types.ts b/src/redux/modal/types.ts index aab5796fb..029ea5cef 100644 --- a/src/redux/modal/types.ts +++ b/src/redux/modal/types.ts @@ -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; diff --git a/src/redux/user/actions.ts b/src/redux/user/actions.ts deleted file mode 100644 index 1056bde94..000000000 --- a/src/redux/user/actions.ts +++ /dev/null @@ -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; diff --git a/src/redux/user/methods.ts b/src/redux/user/methods.ts new file mode 100644 index 000000000..b85cb21b4 --- /dev/null +++ b/src/redux/user/methods.ts @@ -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); +} \ No newline at end of file diff --git a/src/redux/user/reducers.ts b/src/redux/user/reducers.ts index 0349fc29e..076b20fc3 100644 --- a/src/redux/user/reducers.ts +++ b/src/redux/user/reducers.ts @@ -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: "", diff --git a/src/redux/user/types.ts b/src/redux/user/types.ts index 5d50dad61..855d92e3d 100644 --- a/src/redux/user/types.ts +++ b/src/redux/user/types.ts @@ -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; @@ -8,4 +25,6 @@ export interface UserState { export enum LoginStatus { forbidden = "forbidden", ok = "ok" -} \ No newline at end of file +} + +export type UserActions = SetUserAction | ClearUserAction; diff --git a/src/utils/apiUtils.ts b/src/utils/apiUtils.ts new file mode 100644 index 000000000..4dd3bb7ad --- /dev/null +++ b/src/utils/apiUtils.ts @@ -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); + } + } +} \ No newline at end of file diff --git a/src/utils/store.ts b/src/utils/store.ts new file mode 100644 index 000000000..375d2d397 --- /dev/null +++ b/src/utils/store.ts @@ -0,0 +1,4 @@ +import {createStore} from "redux"; +import {allReducers} from "../redux"; + +export const store = createStore(allReducers);