mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-26 03:33:58 -05:00
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:
parent
287d2e2729
commit
d0fc96b929
42 changed files with 173 additions and 182 deletions
|
@ -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`)
|
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`)
|
2. Go inside the repo (e.g. `cd codimd-react-client`)
|
||||||
3. run `yarn install`
|
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.
|
The page will reload if you make edits.
|
||||||
You will also see any lint errors in the console.
|
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.
|
Unit testing is done via jest.
|
||||||
|
|
||||||
1. `yarn test`
|
1. run `yarn test`
|
||||||
|
|
||||||
#### End2End
|
#### End2End
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"baseUrl": "http://localhost:3000/",
|
"baseUrl": "http://localhost:3001/",
|
||||||
"defaultCommandTimeout": 15000,
|
"defaultCommandTimeout": 15000,
|
||||||
"experimentalFetchPolyfill": true
|
"experimentalFetchPolyfill": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ export const banner = {
|
||||||
|
|
||||||
export const branding = {
|
export const branding = {
|
||||||
name: 'ACME Corp',
|
name: 'ACME Corp',
|
||||||
logo: 'http://localhost:3000/acme.png'
|
logo: 'http://localhost:3001/acme.png'
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
|
@ -85,15 +85,16 @@
|
||||||
"use-resize-observer": "6.1.0"
|
"use-resize-observer": "6.1.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"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",
|
"build": "react-scripts build",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"cy:open": "cypress open",
|
"cy:open": "cypress open",
|
||||||
"cy:run:chrome": "cypress run --browser chrome",
|
"cy:run:chrome": "cypress run --browser chrome",
|
||||||
"cy:run:firefox": "cypress run --browser firefox",
|
"cy:run:firefox": "cypress run --browser firefox",
|
||||||
"e2e:chrome": "start-server-and-test start http-get://localhost:3000 cy:run:chrome",
|
"e2e:chrome": "start-server-and-test start http-get://localhost:3001 cy:run:chrome",
|
||||||
"e2e:firefox": "start-server-and-test start http-get://localhost:3000 cy:run:firefox"
|
"e2e:firefox": "start-server-and-test start http-get://localhost:3001 cy:run:firefox"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
},
|
},
|
||||||
"branding": {
|
"branding": {
|
||||||
"name": "ACME Corp",
|
"name": "ACME Corp",
|
||||||
"logo": "http://localhost:3000/acme.png"
|
"logo": "http://localhost:3001/acme.png"
|
||||||
},
|
},
|
||||||
"banner": {
|
"banner": {
|
||||||
"text": "This is the test banner text",
|
"text": "This is the test banner text",
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"backendUrl": "http://localhost:3000"
|
|
||||||
}
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { expectResponseCode, getBackendUrl } from '../utils/apiUtils'
|
import { expectResponseCode, getApiUrl } from '../utils/apiUtils'
|
||||||
import { defaultFetchConfig } from './default'
|
import { defaultFetchConfig } from './default'
|
||||||
|
|
||||||
export const doEmailLogin = async (email: string, password: string): Promise<void> => {
|
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,
|
...defaultFetchConfig,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
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> => {
|
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,
|
...defaultFetchConfig,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
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> => {
|
export const doOpenIdLogin = async (openId: string): Promise<void> => {
|
||||||
const response = await fetch(getBackendUrl() + '/auth/openid', {
|
const response = await fetch(getApiUrl() + '/auth/openid', {
|
||||||
...defaultFetchConfig,
|
...defaultFetchConfig,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
|
|
@ -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
11
src/api/config/index.ts
Normal 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>
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
export interface BackendConfig {
|
export interface Config {
|
||||||
allowAnonymous: boolean,
|
allowAnonymous: boolean,
|
||||||
authProviders: AuthProvidersState,
|
authProviders: AuthProvidersState,
|
||||||
branding: BrandingConfig,
|
branding: BrandingConfig,
|
|
@ -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>
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
export interface FrontendConfig {
|
|
||||||
backendUrl: string
|
|
||||||
}
|
|
|
@ -1,15 +1,15 @@
|
||||||
import { HistoryEntry } from '../components/landing/pages/history/history'
|
import { HistoryEntry } from '../components/landing/pages/history/history'
|
||||||
import { expectResponseCode, getBackendUrl } from '../utils/apiUtils'
|
import { expectResponseCode, getApiUrl } from '../utils/apiUtils'
|
||||||
import { defaultFetchConfig } from './default'
|
import { defaultFetchConfig } from './default'
|
||||||
|
|
||||||
export const getHistory = async (): Promise<HistoryEntry[]> => {
|
export const getHistory = async (): Promise<HistoryEntry[]> => {
|
||||||
const response = await fetch(getBackendUrl() + '/history')
|
const response = await fetch(getApiUrl() + '/history')
|
||||||
expectResponseCode(response)
|
expectResponseCode(response)
|
||||||
return await response.json() as Promise<HistoryEntry[]>
|
return await response.json() as Promise<HistoryEntry[]>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setHistory = async (entries: HistoryEntry[]): Promise<void> => {
|
export const setHistory = async (entries: HistoryEntry[]): Promise<void> => {
|
||||||
const response = await fetch(getBackendUrl() + '/history', {
|
const response = await fetch(getApiUrl() + '/history', {
|
||||||
...defaultFetchConfig,
|
...defaultFetchConfig,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
@ -20,7 +20,7 @@ export const setHistory = async (entries: HistoryEntry[]): Promise<void> => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deleteHistory = async (): Promise<void> => {
|
export const deleteHistory = async (): Promise<void> => {
|
||||||
const response = await fetch(getBackendUrl() + '/history', {
|
const response = await fetch(getApiUrl() + '/history', {
|
||||||
...defaultFetchConfig,
|
...defaultFetchConfig,
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
})
|
})
|
||||||
|
@ -28,7 +28,7 @@ export const deleteHistory = async (): Promise<void> => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateHistoryEntry = async (noteId: string, entry: HistoryEntry): Promise<HistoryEntry> => {
|
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,
|
...defaultFetchConfig,
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: JSON.stringify(entry)
|
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> => {
|
export const deleteHistoryEntry = async (noteId: string): Promise<void> => {
|
||||||
const response = await fetch(getBackendUrl() + '/history/' + noteId, {
|
const response = await fetch(getApiUrl() + '/history/' + noteId, {
|
||||||
...defaultFetchConfig,
|
...defaultFetchConfig,
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { ImageProxyResponse } from '../components/editor/markdown-renderer/replace-components/image/types'
|
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'
|
import { defaultFetchConfig } from './default'
|
||||||
|
|
||||||
export const getProxiedUrl = async (imageUrl: string): Promise<ImageProxyResponse> => {
|
export const getProxiedUrl = async (imageUrl: string): Promise<ImageProxyResponse> => {
|
||||||
const response = await fetch(getBackendUrl() + '/media/proxy', {
|
const response = await fetch(getApiUrl() + '/media/proxy', {
|
||||||
...defaultFetchConfig,
|
...defaultFetchConfig,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { LoginProvider } from '../redux/user/types'
|
import { LoginProvider } from '../redux/user/types'
|
||||||
import { expectResponseCode, getBackendUrl } from '../utils/apiUtils'
|
import { expectResponseCode, getApiUrl } from '../utils/apiUtils'
|
||||||
import { defaultFetchConfig } from './default'
|
import { defaultFetchConfig } from './default'
|
||||||
|
|
||||||
export const getMe = async (): Promise<meResponse> => {
|
export const getMe = async (): Promise<meResponse> => {
|
||||||
|
@ -16,7 +16,7 @@ export interface meResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateDisplayName = async (displayName: string): Promise<void> => {
|
export const updateDisplayName = async (displayName: string): Promise<void> => {
|
||||||
const response = await fetch(getBackendUrl() + '/me', {
|
const response = await fetch(getApiUrl() + '/me', {
|
||||||
...defaultFetchConfig,
|
...defaultFetchConfig,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
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> => {
|
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,
|
...defaultFetchConfig,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
@ -41,7 +41,7 @@ export const changePassword = async (oldPassword: string, newPassword: string):
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deleteUser = async (): Promise<void> => {
|
export const deleteUser = async (): Promise<void> => {
|
||||||
const response = await fetch(getBackendUrl() + '/me', {
|
const response = await fetch(getApiUrl() + '/me', {
|
||||||
...defaultFetchConfig,
|
...defaultFetchConfig,
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { expectResponseCode, getBackendUrl } from '../utils/apiUtils'
|
import { expectResponseCode, getApiUrl } from '../utils/apiUtils'
|
||||||
import { defaultFetchConfig } from './default'
|
import { defaultFetchConfig } from './default'
|
||||||
|
|
||||||
interface LastChange {
|
interface LastChange {
|
||||||
|
@ -19,13 +19,13 @@ export interface Note {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getNote = async (noteId: string): Promise<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)
|
expectResponseCode(response)
|
||||||
return await response.json() as Promise<Note>
|
return await response.json() as Promise<Note>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deleteNote = async (noteId: string): Promise<void> => {
|
export const deleteNote = async (noteId: string): Promise<void> => {
|
||||||
const response = await fetch(getBackendUrl() + `/notes/${noteId}`, {
|
const response = await fetch(getApiUrl() + `/notes/${noteId}`, {
|
||||||
...defaultFetchConfig,
|
...defaultFetchConfig,
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
})
|
})
|
||||||
|
|
|
@ -9,8 +9,7 @@ export const ApplicationLoader: React.FC = ({ children }) => {
|
||||||
const { pathname } = useLocation()
|
const { pathname } = useLocation()
|
||||||
|
|
||||||
const setUpTasks = useCallback(() => {
|
const setUpTasks = useCallback(() => {
|
||||||
const baseUrl: string = window.location.pathname.replace(pathname, '') + '/'
|
const baseUrl: string = window.location.pathname.replace(pathname, '')
|
||||||
console.debug('Base URL is', baseUrl)
|
|
||||||
return createSetUpTaskList(baseUrl)
|
return createSetUpTaskList(baseUrl)
|
||||||
}, [pathname])
|
}, [pathname])
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,21 @@
|
||||||
import { getBackendConfig } from '../../../api/backend-config'
|
import { getConfig } from '../../../api/config'
|
||||||
import { getFrontendConfig } from '../../../api/frontend-config'
|
import { setApiUrl } from '../../../redux/api-url/methods'
|
||||||
import { setBackendConfig } from '../../../redux/backend-config/methods'
|
|
||||||
import { setBanner } from '../../../redux/banner/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'
|
import { getAndSetUser } from '../../../utils/apiUtils'
|
||||||
|
|
||||||
export const loadAllConfig: (baseUrl: string) => Promise<void> = async (baseUrl) => {
|
export const loadAllConfig: (baseUrl: string) => Promise<void> = async (baseUrl) => {
|
||||||
const frontendConfig = await getFrontendConfig(baseUrl)
|
setApiUrl({
|
||||||
if (!frontendConfig) {
|
apiUrl: (process.env.REACT_APP_BACKEND || baseUrl) + '/api/v2'
|
||||||
return Promise.reject(new Error('Frontend config empty!'))
|
})
|
||||||
}
|
|
||||||
setFrontendConfig(frontendConfig)
|
|
||||||
|
|
||||||
const backendConfig = await getBackendConfig()
|
const config = await getConfig()
|
||||||
if (!backendConfig) {
|
if (!config) {
|
||||||
return Promise.reject(new Error('Backend config empty!'))
|
return Promise.reject(new Error('Config empty!'))
|
||||||
}
|
}
|
||||||
setBackendConfig(backendConfig)
|
setConfig(config)
|
||||||
|
|
||||||
const banner = backendConfig.banner
|
const banner = config.banner
|
||||||
if (banner.text !== '') {
|
if (banner.text !== '') {
|
||||||
const lastAcknowledgedTimestamp = window.localStorage.getItem('bannerTimeStamp') || ''
|
const lastAcknowledgedTimestamp = window.localStorage.getItem('bannerTimeStamp') || ''
|
||||||
setBanner({
|
setBanner({
|
||||||
|
|
|
@ -9,7 +9,7 @@ export interface BrandingProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Branding: React.FC<BrandingProps> = ({ inline = false }) => {
|
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
|
const showBranding = !!branding.name || !!branding.logo
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -7,7 +7,7 @@ export interface DocumentTitleProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DocumentTitle: React.FC<DocumentTitleProps> = ({ title }) => {
|
export const DocumentTitle: React.FC<DocumentTitleProps> = ({ title }) => {
|
||||||
const branding = useSelector((state: ApplicationState) => state.backendConfig.branding)
|
const branding = useSelector((state: ApplicationState) => state.config.branding)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = `${title ? title + ' - ' : ''}CodiMD ${branding.name ? ` @ ${branding.name}` : ''}`
|
document.title = `${title ? title + ' - ' : ''}CodiMD ${branding.name ? ` @ ${branding.name}` : ''}`
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { ApplicationState } from '../../../../../redux'
|
||||||
|
|
||||||
export const ImageFrame: React.FC<React.ImgHTMLAttributes<HTMLImageElement>> = ({ alt, src, ...props }) => {
|
export const ImageFrame: React.FC<React.ImgHTMLAttributes<HTMLImageElement>> = ({ alt, src, ...props }) => {
|
||||||
const [imageUrl, setImageUrl] = useState('')
|
const [imageUrl, setImageUrl] = useState('')
|
||||||
const imageProxyEnabled = useSelector((state: ApplicationState) => state.backendConfig.useImageProxy)
|
const imageProxyEnabled = useSelector((state: ApplicationState) => state.config.useImageProxy)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!imageProxyEnabled || !src) {
|
if (!imageProxyEnabled || !src) {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { VersionInfo } from '../version-info/version-info'
|
||||||
export const PoweredByLinks: React.FC = () => {
|
export const PoweredByLinks: React.FC = () => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
|
|
||||||
const config = useSelector((state: ApplicationState) => state.backendConfig)
|
const config = useSelector((state: ApplicationState) => state.config)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
|
|
|
@ -13,7 +13,7 @@ type SignInButtonProps = {
|
||||||
|
|
||||||
export const SignInButton: React.FC<SignInButtonProps> = ({ variant, ...props }) => {
|
export const SignInButton: React.FC<SignInButtonProps> = ({ variant, ...props }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const authProviders = useSelector((state: ApplicationState) => state.backendConfig.authProviders)
|
const authProviders = useSelector((state: ApplicationState) => state.config.authProviders)
|
||||||
return (
|
return (
|
||||||
<ShowIf condition={Object.values(authProviders).includes(true)}>
|
<ShowIf condition={Object.values(authProviders).includes(true)}>
|
||||||
<LinkContainer to="/login" title={t('login.signIn')}>
|
<LinkContainer to="/login" title={t('login.signIn')}>
|
||||||
|
|
|
@ -17,7 +17,7 @@ export const VersionInfo: React.FC = () => {
|
||||||
|
|
||||||
const { t } = useTranslation()
|
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) => (
|
const column = (title: string, version: string, sourceCodeLink: string, issueTrackerLink: string) => (
|
||||||
<Col md={6} className={'flex-column'}>
|
<Col md={6} className={'flex-column'}>
|
||||||
|
|
|
@ -11,7 +11,7 @@ import './cover-buttons.scss'
|
||||||
export const CoverButtons: React.FC = () => {
|
export const CoverButtons: React.FC = () => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
const user = useSelector((state: ApplicationState) => state.user)
|
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) {
|
if (user) {
|
||||||
return null
|
return null
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { getAndSetUser } from '../../../../../utils/apiUtils'
|
||||||
|
|
||||||
export const ViaLdap: React.FC = () => {
|
export const ViaLdap: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
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 [username, setUsername] = useState('')
|
||||||
const [password, setPassword] = useState('')
|
const [password, setPassword] = useState('')
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
import { ApplicationState } from '../../../../../redux'
|
||||||
import { IconName } from '../../../../common/fork-awesome/fork-awesome-icon'
|
import { IconName } from '../../../../common/fork-awesome/fork-awesome-icon'
|
||||||
import { SocialLinkButton } from './social-link-button/social-link-button'
|
import { SocialLinkButton } from './social-link-button/social-link-button'
|
||||||
|
|
||||||
|
@ -13,74 +15,75 @@ export enum OneClickType {
|
||||||
'TWITTER' = 'twitter'
|
'TWITTER' = 'twitter'
|
||||||
}
|
}
|
||||||
|
|
||||||
type OneClick2Map = (oneClickType: OneClickType) => {
|
interface OneClickMetadata {
|
||||||
name: string,
|
name: string,
|
||||||
icon: IconName,
|
icon: IconName,
|
||||||
className: string,
|
className: string,
|
||||||
url: 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) {
|
switch (oneClickType) {
|
||||||
case OneClickType.DROPBOX:
|
case OneClickType.DROPBOX:
|
||||||
return {
|
return {
|
||||||
name: 'Dropbox',
|
name: 'Dropbox',
|
||||||
icon: 'dropbox',
|
icon: 'dropbox',
|
||||||
className: 'btn-social-dropbox',
|
className: 'btn-social-dropbox',
|
||||||
url: buildBackendAuthUrl('dropbox')
|
url: buildBackendAuthUrlWithFirstParameterSet('dropbox')
|
||||||
}
|
}
|
||||||
case OneClickType.FACEBOOK:
|
case OneClickType.FACEBOOK:
|
||||||
return {
|
return {
|
||||||
name: 'Facebook',
|
name: 'Facebook',
|
||||||
icon: 'facebook',
|
icon: 'facebook',
|
||||||
className: 'btn-social-facebook',
|
className: 'btn-social-facebook',
|
||||||
url: buildBackendAuthUrl('facebook')
|
url: buildBackendAuthUrlWithFirstParameterSet('facebook')
|
||||||
}
|
}
|
||||||
case OneClickType.GITHUB:
|
case OneClickType.GITHUB:
|
||||||
return {
|
return {
|
||||||
name: 'GitHub',
|
name: 'GitHub',
|
||||||
icon: 'github',
|
icon: 'github',
|
||||||
className: 'btn-social-github',
|
className: 'btn-social-github',
|
||||||
url: buildBackendAuthUrl('github')
|
url: buildBackendAuthUrlWithFirstParameterSet('github')
|
||||||
}
|
}
|
||||||
case OneClickType.GITLAB:
|
case OneClickType.GITLAB:
|
||||||
return {
|
return {
|
||||||
name: 'GitLab',
|
name: 'GitLab',
|
||||||
icon: 'gitlab',
|
icon: 'gitlab',
|
||||||
className: 'btn-social-gitlab',
|
className: 'btn-social-gitlab',
|
||||||
url: buildBackendAuthUrl('gitlab')
|
url: buildBackendAuthUrlWithFirstParameterSet('gitlab')
|
||||||
}
|
}
|
||||||
case OneClickType.GOOGLE:
|
case OneClickType.GOOGLE:
|
||||||
return {
|
return {
|
||||||
name: 'Google',
|
name: 'Google',
|
||||||
icon: 'google',
|
icon: 'google',
|
||||||
className: 'btn-social-google',
|
className: 'btn-social-google',
|
||||||
url: buildBackendAuthUrl('google')
|
url: buildBackendAuthUrlWithFirstParameterSet('google')
|
||||||
}
|
}
|
||||||
case OneClickType.OAUTH2:
|
case OneClickType.OAUTH2:
|
||||||
return {
|
return {
|
||||||
name: 'OAuth2',
|
name: 'OAuth2',
|
||||||
icon: 'address-card',
|
icon: 'address-card',
|
||||||
className: 'btn-primary',
|
className: 'btn-primary',
|
||||||
url: buildBackendAuthUrl('oauth2')
|
url: buildBackendAuthUrlWithFirstParameterSet('oauth2')
|
||||||
}
|
}
|
||||||
case OneClickType.SAML:
|
case OneClickType.SAML:
|
||||||
return {
|
return {
|
||||||
name: 'SAML',
|
name: 'SAML',
|
||||||
icon: 'users',
|
icon: 'users',
|
||||||
className: 'btn-success',
|
className: 'btn-success',
|
||||||
url: buildBackendAuthUrl('saml')
|
url: buildBackendAuthUrlWithFirstParameterSet('saml')
|
||||||
}
|
}
|
||||||
case OneClickType.TWITTER:
|
case OneClickType.TWITTER:
|
||||||
return {
|
return {
|
||||||
name: 'Twitter',
|
name: 'Twitter',
|
||||||
icon: 'twitter',
|
icon: 'twitter',
|
||||||
className: 'btn-social-twitter',
|
className: 'btn-social-twitter',
|
||||||
url: buildBackendAuthUrl('twitter')
|
url: buildBackendAuthUrlWithFirstParameterSet('twitter')
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
|
@ -98,8 +101,10 @@ export interface ViaOneClickProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ViaOneClick: React.FC<ViaOneClickProps> = ({ oneClickType, optionalName }) => {
|
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
|
const text = optionalName || name
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SocialLinkButton
|
<SocialLinkButton
|
||||||
backgroundClass={className}
|
backgroundClass={className}
|
||||||
|
|
|
@ -12,8 +12,8 @@ import { ViaOpenId } from './auth/via-openid'
|
||||||
|
|
||||||
export const Login: React.FC = () => {
|
export const Login: React.FC = () => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
const authProviders = useSelector((state: ApplicationState) => state.backendConfig.authProviders)
|
const authProviders = useSelector((state: ApplicationState) => state.config.authProviders)
|
||||||
const customAuthNames = useSelector((state: ApplicationState) => state.backendConfig.customAuthNames)
|
const customAuthNames = useSelector((state: ApplicationState) => state.config.customAuthNames)
|
||||||
const userLoginState = useSelector((state: ApplicationState) => state.user)
|
const userLoginState = useSelector((state: ApplicationState) => state.user)
|
||||||
|
|
||||||
const oneClickProviders = [authProviders.dropbox, authProviders.facebook, authProviders.github, authProviders.gitlab,
|
const oneClickProviders = [authProviders.dropbox, authProviders.facebook, authProviders.github, authProviders.gitlab,
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Button, Card, Modal } from 'react-bootstrap'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { deleteUser } from '../../../../../api/me'
|
import { deleteUser } from '../../../../../api/me'
|
||||||
import { clearUser } from '../../../../../redux/user/methods'
|
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'
|
import { ForkAwesomeIcon } from '../../../../common/fork-awesome/fork-awesome-icon'
|
||||||
|
|
||||||
export const ProfileAccountManagement: React.FC = () => {
|
export const ProfileAccountManagement: React.FC = () => {
|
||||||
|
@ -57,7 +57,7 @@ export const ProfileAccountManagement: React.FC = () => {
|
||||||
<Card className="bg-dark mb-4">
|
<Card className="bg-dark mb-4">
|
||||||
<Card.Body>
|
<Card.Body>
|
||||||
<Card.Title><Trans i18nKey="profile.accountManagement"/></Card.Title>
|
<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"/>
|
<ForkAwesomeIcon icon="cloud-download" fixedWidth={true} className="mx-2"/>
|
||||||
<Trans i18nKey="profile.exportUserData"/>
|
<Trans i18nKey="profile.exportUserData"/>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
10
src/redux/api-url/methods.ts
Normal file
10
src/redux/api-url/methods.ts
Normal 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)
|
||||||
|
}
|
15
src/redux/api-url/reducers.ts
Normal file
15
src/redux/api-url/reducers.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
17
src/redux/api-url/types.ts
Normal file
17
src/redux/api-url/types.ts
Normal 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
|
||||||
|
}
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
11
src/redux/config/methods.ts
Normal file
11
src/redux/config/methods.ts
Normal 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)
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
import { Reducer } from 'redux'
|
import { Reducer } from 'redux'
|
||||||
import { BackendConfig } from '../../api/backend-config/types'
|
import { Config } from '../../api/config/types'
|
||||||
import { BackendConfigActions, BackendConfigActionType, SetBackendConfigAction } from './types'
|
import { ConfigActions, ConfigActionType, SetConfigAction } from './types'
|
||||||
|
|
||||||
export const initialState: BackendConfig = {
|
export const initialState: Config = {
|
||||||
allowAnonymous: true,
|
allowAnonymous: true,
|
||||||
authProviders: {
|
authProviders: {
|
||||||
facebook: false,
|
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) {
|
switch (action.type) {
|
||||||
case BackendConfigActionType.SET_BACKEND_CONFIG:
|
case ConfigActionType.SET_CONFIG:
|
||||||
return (action as SetBackendConfigAction).state
|
return (action as SetConfigAction).state
|
||||||
default:
|
default:
|
||||||
return state
|
return state
|
||||||
}
|
}
|
14
src/redux/config/types.ts
Normal file
14
src/redux/config/types.ts
Normal 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;
|
||||||
|
}
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -1,27 +1,27 @@
|
||||||
import { combineReducers, Reducer } from 'redux'
|
import { combineReducers, Reducer } from 'redux'
|
||||||
import { BackendConfig } from '../api/backend-config/types'
|
import { Config } from '../api/config/types'
|
||||||
import { FrontendConfig } from '../api/frontend-config/types'
|
import { ApiUrlReducer } from './api-url/reducers'
|
||||||
import { BackendConfigReducer } from './backend-config/reducers'
|
import { ApiUrlObject } from './api-url/types'
|
||||||
import { BannerReducer } from './banner/reducers'
|
import { BannerReducer } from './banner/reducers'
|
||||||
import { BannerState } from './banner/types'
|
import { BannerState } from './banner/types'
|
||||||
|
import { ConfigReducer } from './config/reducers'
|
||||||
import { EditorConfigReducer } from './editor/reducers'
|
import { EditorConfigReducer } from './editor/reducers'
|
||||||
import { EditorConfig } from './editor/types'
|
import { EditorConfig } from './editor/types'
|
||||||
import { FrontendConfigReducer } from './frontend-config/reducers'
|
|
||||||
import { UserReducer } from './user/reducers'
|
import { UserReducer } from './user/reducers'
|
||||||
import { MaybeUserState } from './user/types'
|
import { MaybeUserState } from './user/types'
|
||||||
|
|
||||||
export interface ApplicationState {
|
export interface ApplicationState {
|
||||||
user: MaybeUserState;
|
user: MaybeUserState;
|
||||||
backendConfig: BackendConfig;
|
config: Config;
|
||||||
banner: BannerState;
|
banner: BannerState;
|
||||||
frontendConfig: FrontendConfig;
|
apiUrl: ApiUrlObject;
|
||||||
editorConfig: EditorConfig;
|
editorConfig: EditorConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const allReducers: Reducer<ApplicationState> = combineReducers<ApplicationState>({
|
export const allReducers: Reducer<ApplicationState> = combineReducers<ApplicationState>({
|
||||||
user: UserReducer,
|
user: UserReducer,
|
||||||
backendConfig: BackendConfigReducer,
|
config: ConfigReducer,
|
||||||
banner: BannerReducer,
|
banner: BannerReducer,
|
||||||
frontendConfig: FrontendConfigReducer,
|
apiUrl: ApiUrlReducer,
|
||||||
editorConfig: EditorConfigReducer
|
editorConfig: EditorConfigReducer
|
||||||
})
|
})
|
||||||
|
|
|
@ -12,8 +12,8 @@ export const getAndSetUser: () => (Promise<void>) = async () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getBackendUrl: (() => string) = () => {
|
export const getApiUrl = (): string => {
|
||||||
return store.getState().frontendConfig.backendUrl
|
return store.getState().apiUrl.apiUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
export const expectResponseCode = (response: Response, code = 200): void => {
|
export const expectResponseCode = (response: Response, code = 200): void => {
|
||||||
|
|
Loading…
Reference in a new issue