feat: fetch frontend config in server side rendering

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2023-04-04 17:27:20 +02:00
parent 312d1adf6f
commit 24f1b2a361
41 changed files with 270 additions and 220 deletions

View file

@ -1,10 +1,10 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React, { createContext, useState } from 'react'
import type { PropsWithChildren } from 'react'
import React, { createContext, useState } from 'react'
export interface BaseUrls {
renderer: string
@ -28,9 +28,8 @@ export const BaseUrlContextProvider: React.FC<PropsWithChildren<BaseUrlContextPr
children
}) => {
const [baseUrlState] = useState<undefined | BaseUrls>(() => baseUrls)
return baseUrlState === undefined ? (
<div className={'text-white'}>HedgeDoc is not configured correctly! Please check the server log.</div>
<span className={'text-white bg-dark'}>HedgeDoc is not configured correctly! Please check the server log.</span>
) : (
<baseUrlContext.Provider value={baseUrlState}>{children}</baseUrlContext.Provider>
)

View file

@ -1,9 +1,9 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useApplicationState } from '../../../hooks/common/use-application-state'
import { useFrontendConfig } from '../frontend-config-context/use-frontend-config'
import { ShowIf } from '../show-if/show-if'
import styles from './branding.module.scss'
import React, { useMemo } from 'react'
@ -21,7 +21,7 @@ export interface BrandingProps {
* @param delimiter If the delimiter between the HedgeDoc logo and the branding should be shown.
*/
export const Branding: React.FC<BrandingProps> = ({ inline = false, delimiter = true }) => {
const branding = useApplicationState((state) => state.config.branding)
const branding = useFrontendConfig().branding
const showBranding = !!branding.name || !!branding.logo
const brandingDom = useMemo(() => {

View file

@ -0,0 +1,9 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { FrontendConfig } from '../../../api/config/types'
import { createContext } from 'react'
export const frontendConfigContext = createContext<FrontendConfig | undefined>(undefined)

View file

@ -0,0 +1,45 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { getConfig } from '../../../api/config'
import type { FrontendConfig } from '../../../api/config/types'
import { useBaseUrl } from '../../../hooks/common/use-base-url'
import { Logger } from '../../../utils/logger'
import { frontendConfigContext } from './context'
import type { PropsWithChildren } from 'react'
import React, { useEffect, useState } from 'react'
const logger = new Logger('FrontendConfigContextProvider')
interface FrontendConfigContextProviderProps extends PropsWithChildren {
config?: FrontendConfig
}
/**
* Provides the given frontend configuration in a context or renders an error message otherwise.
*
* @param config the frontend config to provoide
* @param children the react elements to show if the config is valid
*/
export const FrontendConfigContextProvider: React.FC<FrontendConfigContextProviderProps> = ({ config, children }) => {
const [configState, setConfigState] = useState<undefined | FrontendConfig>(() => config)
const baseUrl = useBaseUrl()
useEffect(() => {
if (config === undefined && configState === undefined) {
logger.debug('Fetching Config client side')
getConfig(baseUrl)
.then((config) => setConfigState(config))
.catch((error) => logger.error(error))
}
}, [baseUrl, config, configState])
return configState === undefined ? (
<span className={'text-white bg-dark'}>No frontend config received! Please check the server log.</span>
) : (
<frontendConfigContext.Provider value={configState}>{children}</frontendConfigContext.Provider>
)
}

View file

@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { FrontendConfig } from '../../../api/config/types'
import { frontendConfigContext } from './context'
import { Optional } from '@mrdrogdrog/optional'
import { useContext } from 'react'
/**
* Retrieves the current frontend config from the next react context.
*/
export const useFrontendConfig = (): FrontendConfig => {
return Optional.ofNullable(useContext(frontendConfigContext)).orElseThrow(
() => new Error('No frontend config context found. Did you forget to use the provider component?')
)
}

View file

@ -22,7 +22,7 @@ jest.mock('../../../hooks/common/use-base-url')
describe('motd modal', () => {
beforeAll(async () => {
jest.spyOn(UseBaseUrlModule, 'useBaseUrl').mockImplementation(() => 'https://example.org')
jest.spyOn(UseBaseUrlModule, 'useBaseUrl').mockImplementation(() => new URL('https://example.org'))
await mockI18n()
})