rework how the frontend is started (#361)

renamed frontend-config to api-url
renamed backend-config to config
removed api call to set frontend-config as the frontend either know where the backend is as it is delivered by it or get's this information via the enviroment variable REACT_APP_BACKEND
always start the client on Port 3001 as the backend will run on 3000 during development. changed the port on multiple occasions to accommodate for this
added package.json script 'start:dev'
changed README to better explain how to run backend and frontend side-by-side
This commit is contained in:
Philip Molares 2020-07-29 22:58:01 +02:00 committed by GitHub
parent 287d2e2729
commit d0fc96b929
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 173 additions and 182 deletions

View file

@ -13,9 +13,11 @@ You'll need at least Node 10 (we recommend 12). We use [yarn](https://yarnpkg.co
1. Clone this repo (e.g. `git clone https://github.com/codimd/react-client.git codimd-react-client`)
2. Go inside the repo (e.g. `cd codimd-react-client`)
3. run `yarn install`
4. run `yarn start`
4. Either run
- `yarn start:dev` (expects [a server](https://github.com/codimd/server/tree/develop) running under [http://localhost:3000](http://localhost:3000))
- `yarn start` (makes all api calls to the same domain the react-client runs on (normally [http://localhost:3001](http://localhost:3001) ))
This should run the app in the development mode and open [http://localhost:3000](http://localhost:3000) in your browser.
This should run the app in the development mode and open [http://localhost:3001](http://localhost:3001) in your browser.
The page will reload if you make edits.
You will also see any lint errors in the console.
@ -26,7 +28,7 @@ You will also see any lint errors in the console.
Unit testing is done via jest.
1. `yarn test`
1. run `yarn test`
#### End2End

View file

@ -1,5 +1,5 @@
{
"baseUrl": "http://localhost:3000/",
"baseUrl": "http://localhost:3001/",
"defaultCommandTimeout": 15000,
"experimentalFetchPolyfill": true
}

View file

@ -5,7 +5,7 @@ export const banner = {
export const branding = {
name: 'ACME Corp',
logo: 'http://localhost:3000/acme.png'
logo: 'http://localhost:3001/acme.png'
}
beforeEach(() => {

View file

@ -85,15 +85,16 @@
"use-resize-observer": "6.1.0"
},
"scripts": {
"start": "react-scripts start",
"start": "PORT=3001 react-scripts start",
"start:dev": "REACT_APP_BACKEND=http://localhost:3000 yarn start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"cy:open": "cypress open",
"cy:run:chrome": "cypress run --browser chrome",
"cy:run:firefox": "cypress run --browser firefox",
"e2e:chrome": "start-server-and-test start http-get://localhost:3000 cy:run:chrome",
"e2e:firefox": "start-server-and-test start http-get://localhost:3000 cy:run:firefox"
"e2e:chrome": "start-server-and-test start http-get://localhost:3001 cy:run:chrome",
"e2e:firefox": "start-server-and-test start http-get://localhost:3001 cy:run:firefox"
},
"eslintConfig": {
"parserOptions": {

View file

@ -15,7 +15,7 @@
},
"branding": {
"name": "ACME Corp",
"logo": "http://localhost:3000/acme.png"
"logo": "http://localhost:3001/acme.png"
},
"banner": {
"text": "This is the test banner text",

View file

@ -1,3 +0,0 @@
{
"backendUrl": "http://localhost:3000"
}

View file

@ -1,8 +1,8 @@
import { expectResponseCode, getBackendUrl } from '../utils/apiUtils'
import { expectResponseCode, getApiUrl } from '../utils/apiUtils'
import { defaultFetchConfig } from './default'
export const doEmailLogin = async (email: string, password: string): Promise<void> => {
const response = await fetch(getBackendUrl() + '/auth/email', {
const response = await fetch(getApiUrl() + '/auth/email', {
...defaultFetchConfig,
method: 'POST',
body: JSON.stringify({
@ -15,7 +15,7 @@ export const doEmailLogin = async (email: string, password: string): Promise<voi
}
export const doLdapLogin = async (username: string, password: string): Promise<void> => {
const response = await fetch(getBackendUrl() + '/auth/ldap', {
const response = await fetch(getApiUrl() + '/auth/ldap', {
...defaultFetchConfig,
method: 'POST',
body: JSON.stringify({
@ -28,7 +28,7 @@ export const doLdapLogin = async (username: string, password: string): Promise<v
}
export const doOpenIdLogin = async (openId: string): Promise<void> => {
const response = await fetch(getBackendUrl() + '/auth/openid', {
const response = await fetch(getApiUrl() + '/auth/openid', {
...defaultFetchConfig,
method: 'POST',
body: JSON.stringify({

View file

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

11
src/api/config/index.ts Normal file
View file

@ -0,0 +1,11 @@
import { expectResponseCode, getApiUrl } from '../../utils/apiUtils'
import { defaultFetchConfig } from '../default'
import { Config } from './types'
export const getConfig = async (): Promise<Config> => {
const response = await fetch(getApiUrl() + '/config', {
...defaultFetchConfig
})
expectResponseCode(response)
return await response.json() as Promise<Config>
}

View file

@ -1,4 +1,4 @@
export interface BackendConfig {
export interface Config {
allowAnonymous: boolean,
authProviders: AuthProvidersState,
branding: BrandingConfig,

View file

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

View file

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

View file

@ -1,15 +1,15 @@
import { HistoryEntry } from '../components/landing/pages/history/history'
import { expectResponseCode, getBackendUrl } from '../utils/apiUtils'
import { expectResponseCode, getApiUrl } from '../utils/apiUtils'
import { defaultFetchConfig } from './default'
export const getHistory = async (): Promise<HistoryEntry[]> => {
const response = await fetch(getBackendUrl() + '/history')
const response = await fetch(getApiUrl() + '/history')
expectResponseCode(response)
return await response.json() as Promise<HistoryEntry[]>
}
export const setHistory = async (entries: HistoryEntry[]): Promise<void> => {
const response = await fetch(getBackendUrl() + '/history', {
const response = await fetch(getApiUrl() + '/history', {
...defaultFetchConfig,
method: 'POST',
body: JSON.stringify({
@ -20,7 +20,7 @@ export const setHistory = async (entries: HistoryEntry[]): Promise<void> => {
}
export const deleteHistory = async (): Promise<void> => {
const response = await fetch(getBackendUrl() + '/history', {
const response = await fetch(getApiUrl() + '/history', {
...defaultFetchConfig,
method: 'DELETE'
})
@ -28,7 +28,7 @@ export const deleteHistory = async (): Promise<void> => {
}
export const updateHistoryEntry = async (noteId: string, entry: HistoryEntry): Promise<HistoryEntry> => {
const response = await fetch(getBackendUrl() + '/history/' + noteId, {
const response = await fetch(getApiUrl() + '/history/' + noteId, {
...defaultFetchConfig,
method: 'PUT',
body: JSON.stringify(entry)
@ -38,7 +38,7 @@ export const updateHistoryEntry = async (noteId: string, entry: HistoryEntry): P
}
export const deleteHistoryEntry = async (noteId: string): Promise<void> => {
const response = await fetch(getBackendUrl() + '/history/' + noteId, {
const response = await fetch(getApiUrl() + '/history/' + noteId, {
...defaultFetchConfig,
method: 'DELETE'
})

View file

@ -1,9 +1,9 @@
import { ImageProxyResponse } from '../components/editor/markdown-renderer/replace-components/image/types'
import { expectResponseCode, getBackendUrl } from '../utils/apiUtils'
import { expectResponseCode, getApiUrl } from '../utils/apiUtils'
import { defaultFetchConfig } from './default'
export const getProxiedUrl = async (imageUrl: string): Promise<ImageProxyResponse> => {
const response = await fetch(getBackendUrl() + '/media/proxy', {
const response = await fetch(getApiUrl() + '/media/proxy', {
...defaultFetchConfig,
method: 'POST',
body: JSON.stringify({

View file

@ -1,5 +1,5 @@
import { LoginProvider } from '../redux/user/types'
import { expectResponseCode, getBackendUrl } from '../utils/apiUtils'
import { expectResponseCode, getApiUrl } from '../utils/apiUtils'
import { defaultFetchConfig } from './default'
export const getMe = async (): Promise<meResponse> => {
@ -16,7 +16,7 @@ export interface meResponse {
}
export const updateDisplayName = async (displayName: string): Promise<void> => {
const response = await fetch(getBackendUrl() + '/me', {
const response = await fetch(getApiUrl() + '/me', {
...defaultFetchConfig,
method: 'POST',
body: JSON.stringify({
@ -28,7 +28,7 @@ export const updateDisplayName = async (displayName: string): Promise<void> => {
}
export const changePassword = async (oldPassword: string, newPassword: string): Promise<void> => {
const response = await fetch(getBackendUrl() + '/me/password', {
const response = await fetch(getApiUrl() + '/me/password', {
...defaultFetchConfig,
method: 'POST',
body: JSON.stringify({
@ -41,7 +41,7 @@ export const changePassword = async (oldPassword: string, newPassword: string):
}
export const deleteUser = async (): Promise<void> => {
const response = await fetch(getBackendUrl() + '/me', {
const response = await fetch(getApiUrl() + '/me', {
...defaultFetchConfig,
method: 'DELETE'
})

View file

@ -1,4 +1,4 @@
import { expectResponseCode, getBackendUrl } from '../utils/apiUtils'
import { expectResponseCode, getApiUrl } from '../utils/apiUtils'
import { defaultFetchConfig } from './default'
interface LastChange {
@ -19,13 +19,13 @@ export interface Note {
}
export const getNote = async (noteId: string): Promise<Note> => {
const response = await fetch(getBackendUrl() + `/notes/${noteId}`)
const response = await fetch(getApiUrl() + `/notes/${noteId}`)
expectResponseCode(response)
return await response.json() as Promise<Note>
}
export const deleteNote = async (noteId: string): Promise<void> => {
const response = await fetch(getBackendUrl() + `/notes/${noteId}`, {
const response = await fetch(getApiUrl() + `/notes/${noteId}`, {
...defaultFetchConfig,
method: 'DELETE'
})

View file

@ -9,8 +9,7 @@ export const ApplicationLoader: React.FC = ({ children }) => {
const { pathname } = useLocation()
const setUpTasks = useCallback(() => {
const baseUrl: string = window.location.pathname.replace(pathname, '') + '/'
console.debug('Base URL is', baseUrl)
const baseUrl: string = window.location.pathname.replace(pathname, '')
return createSetUpTaskList(baseUrl)
}, [pathname])

View file

@ -1,24 +1,21 @@
import { getBackendConfig } from '../../../api/backend-config'
import { getFrontendConfig } from '../../../api/frontend-config'
import { setBackendConfig } from '../../../redux/backend-config/methods'
import { getConfig } from '../../../api/config'
import { setApiUrl } from '../../../redux/api-url/methods'
import { setBanner } from '../../../redux/banner/methods'
import { setFrontendConfig } from '../../../redux/frontend-config/methods'
import { setConfig } from '../../../redux/config/methods'
import { getAndSetUser } from '../../../utils/apiUtils'
export const loadAllConfig: (baseUrl: string) => Promise<void> = async (baseUrl) => {
const frontendConfig = await getFrontendConfig(baseUrl)
if (!frontendConfig) {
return Promise.reject(new Error('Frontend config empty!'))
}
setFrontendConfig(frontendConfig)
setApiUrl({
apiUrl: (process.env.REACT_APP_BACKEND || baseUrl) + '/api/v2'
})
const backendConfig = await getBackendConfig()
if (!backendConfig) {
return Promise.reject(new Error('Backend config empty!'))
const config = await getConfig()
if (!config) {
return Promise.reject(new Error('Config empty!'))
}
setBackendConfig(backendConfig)
setConfig(config)
const banner = backendConfig.banner
const banner = config.banner
if (banner.text !== '') {
const lastAcknowledgedTimestamp = window.localStorage.getItem('bannerTimeStamp') || ''
setBanner({

View file

@ -9,7 +9,7 @@ export interface BrandingProps {
}
export const Branding: React.FC<BrandingProps> = ({ inline = false }) => {
const branding = useSelector((state: ApplicationState) => state.backendConfig.branding)
const branding = useSelector((state: ApplicationState) => state.config.branding)
const showBranding = !!branding.name || !!branding.logo
return (

View file

@ -7,7 +7,7 @@ export interface DocumentTitleProps {
}
export const DocumentTitle: React.FC<DocumentTitleProps> = ({ title }) => {
const branding = useSelector((state: ApplicationState) => state.backendConfig.branding)
const branding = useSelector((state: ApplicationState) => state.config.branding)
useEffect(() => {
document.title = `${title ? title + ' - ' : ''}CodiMD ${branding.name ? ` @ ${branding.name}` : ''}`

View file

@ -5,7 +5,7 @@ import { ApplicationState } from '../../../../../redux'
export const ImageFrame: React.FC<React.ImgHTMLAttributes<HTMLImageElement>> = ({ alt, src, ...props }) => {
const [imageUrl, setImageUrl] = useState('')
const imageProxyEnabled = useSelector((state: ApplicationState) => state.backendConfig.useImageProxy)
const imageProxyEnabled = useSelector((state: ApplicationState) => state.config.useImageProxy)
useEffect(() => {
if (!imageProxyEnabled || !src) {

View file

@ -10,7 +10,7 @@ import { VersionInfo } from '../version-info/version-info'
export const PoweredByLinks: React.FC = () => {
useTranslation()
const config = useSelector((state: ApplicationState) => state.backendConfig)
const config = useSelector((state: ApplicationState) => state.config)
return (
<p>

View file

@ -13,7 +13,7 @@ type SignInButtonProps = {
export const SignInButton: React.FC<SignInButtonProps> = ({ variant, ...props }) => {
const { t } = useTranslation()
const authProviders = useSelector((state: ApplicationState) => state.backendConfig.authProviders)
const authProviders = useSelector((state: ApplicationState) => state.config.authProviders)
return (
<ShowIf condition={Object.values(authProviders).includes(true)}>
<LinkContainer to="/login" title={t('login.signIn')}>

View file

@ -17,7 +17,7 @@ export const VersionInfo: React.FC = () => {
const { t } = useTranslation()
const serverVersion = useSelector((state: ApplicationState) => state.backendConfig.version)
const serverVersion = useSelector((state: ApplicationState) => state.config.version)
const column = (title: string, version: string, sourceCodeLink: string, issueTrackerLink: string) => (
<Col md={6} className={'flex-column'}>

View file

@ -11,7 +11,7 @@ import './cover-buttons.scss'
export const CoverButtons: React.FC = () => {
useTranslation()
const user = useSelector((state: ApplicationState) => state.user)
const authProviders = useSelector((state: ApplicationState) => state.backendConfig.authProviders)
const authProviders = useSelector((state: ApplicationState) => state.config.authProviders)
if (user) {
return null

View file

@ -9,7 +9,7 @@ import { getAndSetUser } from '../../../../../utils/apiUtils'
export const ViaLdap: React.FC = () => {
const { t } = useTranslation()
const ldapCustomName = useSelector((state: ApplicationState) => state.backendConfig.customAuthNames.ldap)
const ldapCustomName = useSelector((state: ApplicationState) => state.config.customAuthNames.ldap)
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')

View file

@ -1,4 +1,6 @@
import React from 'react'
import { useSelector } from 'react-redux'
import { ApplicationState } from '../../../../../redux'
import { IconName } from '../../../../common/fork-awesome/fork-awesome-icon'
import { SocialLinkButton } from './social-link-button/social-link-button'
@ -13,74 +15,75 @@ export enum OneClickType {
'TWITTER' = 'twitter'
}
type OneClick2Map = (oneClickType: OneClickType) => {
interface OneClickMetadata {
name: string,
icon: IconName,
className: string,
url: string
};
const buildBackendAuthUrl = (backendName: string) => {
return `https://localhost:3000/auth/${backendName}`
}
const getMetadata: OneClick2Map = (oneClickType: OneClickType) => {
const buildBackendAuthUrl = (backendUrl: string, backendName: string): string => {
return `${backendUrl}/auth/${backendName}`
}
const getMetadata = (backendUrl: string, oneClickType: OneClickType): OneClickMetadata => {
const buildBackendAuthUrlWithFirstParameterSet = (backendName: string): string => buildBackendAuthUrl(backendUrl, backendName)
switch (oneClickType) {
case OneClickType.DROPBOX:
return {
name: 'Dropbox',
icon: 'dropbox',
className: 'btn-social-dropbox',
url: buildBackendAuthUrl('dropbox')
url: buildBackendAuthUrlWithFirstParameterSet('dropbox')
}
case OneClickType.FACEBOOK:
return {
name: 'Facebook',
icon: 'facebook',
className: 'btn-social-facebook',
url: buildBackendAuthUrl('facebook')
url: buildBackendAuthUrlWithFirstParameterSet('facebook')
}
case OneClickType.GITHUB:
return {
name: 'GitHub',
icon: 'github',
className: 'btn-social-github',
url: buildBackendAuthUrl('github')
url: buildBackendAuthUrlWithFirstParameterSet('github')
}
case OneClickType.GITLAB:
return {
name: 'GitLab',
icon: 'gitlab',
className: 'btn-social-gitlab',
url: buildBackendAuthUrl('gitlab')
url: buildBackendAuthUrlWithFirstParameterSet('gitlab')
}
case OneClickType.GOOGLE:
return {
name: 'Google',
icon: 'google',
className: 'btn-social-google',
url: buildBackendAuthUrl('google')
url: buildBackendAuthUrlWithFirstParameterSet('google')
}
case OneClickType.OAUTH2:
return {
name: 'OAuth2',
icon: 'address-card',
className: 'btn-primary',
url: buildBackendAuthUrl('oauth2')
url: buildBackendAuthUrlWithFirstParameterSet('oauth2')
}
case OneClickType.SAML:
return {
name: 'SAML',
icon: 'users',
className: 'btn-success',
url: buildBackendAuthUrl('saml')
url: buildBackendAuthUrlWithFirstParameterSet('saml')
}
case OneClickType.TWITTER:
return {
name: 'Twitter',
icon: 'twitter',
className: 'btn-social-twitter',
url: buildBackendAuthUrl('twitter')
url: buildBackendAuthUrlWithFirstParameterSet('twitter')
}
default:
return {
@ -98,8 +101,10 @@ export interface ViaOneClickProps {
}
const ViaOneClick: React.FC<ViaOneClickProps> = ({ oneClickType, optionalName }) => {
const { name, icon, className, url } = getMetadata(oneClickType)
const backendUrl = useSelector((state: ApplicationState) => state.apiUrl.apiUrl)
const { name, icon, className, url } = getMetadata(backendUrl, oneClickType)
const text = optionalName || name
return (
<SocialLinkButton
backgroundClass={className}

View file

@ -12,8 +12,8 @@ import { ViaOpenId } from './auth/via-openid'
export const Login: React.FC = () => {
useTranslation()
const authProviders = useSelector((state: ApplicationState) => state.backendConfig.authProviders)
const customAuthNames = useSelector((state: ApplicationState) => state.backendConfig.customAuthNames)
const authProviders = useSelector((state: ApplicationState) => state.config.authProviders)
const customAuthNames = useSelector((state: ApplicationState) => state.config.customAuthNames)
const userLoginState = useSelector((state: ApplicationState) => state.user)
const oneClickProviders = [authProviders.dropbox, authProviders.facebook, authProviders.github, authProviders.gitlab,

View file

@ -3,7 +3,7 @@ import { Button, Card, Modal } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { deleteUser } from '../../../../../api/me'
import { clearUser } from '../../../../../redux/user/methods'
import { getBackendUrl } from '../../../../../utils/apiUtils'
import { getApiUrl } from '../../../../../utils/apiUtils'
import { ForkAwesomeIcon } from '../../../../common/fork-awesome/fork-awesome-icon'
export const ProfileAccountManagement: React.FC = () => {
@ -57,7 +57,7 @@ export const ProfileAccountManagement: React.FC = () => {
<Card className="bg-dark mb-4">
<Card.Body>
<Card.Title><Trans i18nKey="profile.accountManagement"/></Card.Title>
<Button variant="secondary" block href={getBackendUrl() + '/me/export'} className="mb-2">
<Button variant="secondary" block href={getApiUrl() + '/me/export'} className="mb-2">
<ForkAwesomeIcon icon="cloud-download" fixedWidth={true} className="mx-2"/>
<Trans i18nKey="profile.exportUserData"/>
</Button>

View file

@ -0,0 +1,10 @@
import { store } from '../../utils/store'
import { ApiUrlActionType, ApiUrlObject, SetApiUrlAction } from './types'
export const setApiUrl = (state: ApiUrlObject): void => {
const action: SetApiUrlAction = {
type: ApiUrlActionType.SET_API_URL,
state
}
store.dispatch(action)
}

View file

@ -0,0 +1,15 @@
import { Reducer } from 'redux'
import { ApiUrlActions, ApiUrlActionType, ApiUrlObject, SetApiUrlAction } from './types'
export const initialState: ApiUrlObject = {
apiUrl: ''
}
export const ApiUrlReducer: Reducer<ApiUrlObject, ApiUrlActions> = (state: ApiUrlObject = initialState, action: ApiUrlActions) => {
switch (action.type) {
case ApiUrlActionType.SET_API_URL:
return (action as SetApiUrlAction).state
default:
return state
}
}

View file

@ -0,0 +1,17 @@
import { Action } from 'redux'
export enum ApiUrlActionType {
SET_API_URL = 'api-url/set'
}
export interface ApiUrlActions extends Action<ApiUrlActionType> {
type: ApiUrlActionType;
}
export interface SetApiUrlAction extends ApiUrlActions {
state: ApiUrlObject;
}
export interface ApiUrlObject {
apiUrl: string
}

View file

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

View file

@ -1,14 +0,0 @@
import { Action } from 'redux'
import { BackendConfig } from '../../api/backend-config/types'
export enum BackendConfigActionType {
SET_BACKEND_CONFIG = 'backend-config/set'
}
export interface BackendConfigActions extends Action<BackendConfigActionType>{
type: BackendConfigActionType;
}
export interface SetBackendConfigAction extends BackendConfigActions {
state: BackendConfig;
}

View file

@ -0,0 +1,11 @@
import { Config } from '../../api/config/types'
import { store } from '../../utils/store'
import { ConfigActionType, SetConfigAction } from './types'
export const setConfig = (state: Config): void => {
const action: SetConfigAction = {
type: ConfigActionType.SET_CONFIG,
state: state
}
store.dispatch(action)
}

View file

@ -1,8 +1,8 @@
import { Reducer } from 'redux'
import { BackendConfig } from '../../api/backend-config/types'
import { BackendConfigActions, BackendConfigActionType, SetBackendConfigAction } from './types'
import { Config } from '../../api/config/types'
import { ConfigActions, ConfigActionType, SetConfigAction } from './types'
export const initialState: BackendConfig = {
export const initialState: Config = {
allowAnonymous: true,
authProviders: {
facebook: false,
@ -43,10 +43,10 @@ export const initialState: BackendConfig = {
}
}
export const BackendConfigReducer: Reducer<(BackendConfig), BackendConfigActions> = (state: (BackendConfig) = initialState, action: BackendConfigActions) => {
export const ConfigReducer: Reducer<Config, ConfigActions> = (state: Config = initialState, action: ConfigActions) => {
switch (action.type) {
case BackendConfigActionType.SET_BACKEND_CONFIG:
return (action as SetBackendConfigAction).state
case ConfigActionType.SET_CONFIG:
return (action as SetConfigAction).state
default:
return state
}

14
src/redux/config/types.ts Normal file
View file

@ -0,0 +1,14 @@
import { Action } from 'redux'
import { Config } from '../../api/config/types'
export enum ConfigActionType {
SET_CONFIG = 'config/set'
}
export interface ConfigActions extends Action<ConfigActionType> {
type: ConfigActionType;
}
export interface SetConfigAction extends ConfigActions {
state: Config;
}

View file

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

View file

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

View file

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

View file

@ -1,27 +1,27 @@
import { combineReducers, Reducer } from 'redux'
import { BackendConfig } from '../api/backend-config/types'
import { FrontendConfig } from '../api/frontend-config/types'
import { BackendConfigReducer } from './backend-config/reducers'
import { Config } from '../api/config/types'
import { ApiUrlReducer } from './api-url/reducers'
import { ApiUrlObject } from './api-url/types'
import { BannerReducer } from './banner/reducers'
import { BannerState } from './banner/types'
import { ConfigReducer } from './config/reducers'
import { EditorConfigReducer } from './editor/reducers'
import { EditorConfig } from './editor/types'
import { FrontendConfigReducer } from './frontend-config/reducers'
import { UserReducer } from './user/reducers'
import { MaybeUserState } from './user/types'
export interface ApplicationState {
user: MaybeUserState;
backendConfig: BackendConfig;
config: Config;
banner: BannerState;
frontendConfig: FrontendConfig;
apiUrl: ApiUrlObject;
editorConfig: EditorConfig;
}
export const allReducers: Reducer<ApplicationState> = combineReducers<ApplicationState>({
user: UserReducer,
backendConfig: BackendConfigReducer,
config: ConfigReducer,
banner: BannerReducer,
frontendConfig: FrontendConfigReducer,
apiUrl: ApiUrlReducer,
editorConfig: EditorConfigReducer
})

View file

@ -12,8 +12,8 @@ export const getAndSetUser: () => (Promise<void>) = async () => {
})
}
export const getBackendUrl: (() => string) = () => {
return store.getState().frontendConfig.backendUrl
export const getApiUrl = (): string => {
return store.getState().apiUrl.apiUrl
}
export const expectResponseCode = (response: Response, code = 200): void => {