(frontend) initialize application from remote configs

Query API to get the App's configs.
Replacement for env variables, thus with a single image,
we can dynamically update frontend's behavior.

Code inspired by Marsha. Not sure having a single component
returning null is a good idea, to be discussed.
This commit is contained in:
lebaudantoine
2024-09-24 23:00:01 +02:00
committed by aleb_the_flash
parent e591d09b00
commit 8cc2cc83c6
6 changed files with 67 additions and 33 deletions

View File

@@ -11,23 +11,15 @@ import { Layout } from './layout/Layout'
import { NotFoundScreen } from './components/NotFoundScreen' import { NotFoundScreen } from './components/NotFoundScreen'
import { routes } from './routes' import { routes } from './routes'
import './i18n/init' import './i18n/init'
import { silenceLiveKitLogs } from '@/utils/livekit.ts'
import { queryClient } from '@/api/queryClient' import { queryClient } from '@/api/queryClient'
import { useAnalytics } from '@/features/analytics/hooks/useAnalytics' import { AppInitialization } from '@/components/AppInitialization'
import { useSupport } from '@/features/support/hooks/useSupport'
function App() { function App() {
const { i18n } = useTranslation() const { i18n } = useTranslation()
useLang(i18n.language) useLang(i18n.language)
const isProduction = import.meta.env.PROD
silenceLiveKitLogs(isProduction)
useAnalytics()
useSupport()
return ( return (
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<AppInitialization />
<Suspense fallback={null}> <Suspense fallback={null}>
<I18nProvider locale={i18n.language}> <I18nProvider locale={i18n.language}>
<Layout> <Layout>

View File

@@ -1,4 +1,5 @@
export const keys = { export const keys = {
user: 'user', user: 'user',
room: 'room', room: 'room',
config: 'config',
} }

View File

@@ -0,0 +1,26 @@
import { fetchApi } from './fetchApi'
import { keys } from './queryKeys'
import { useQuery } from '@tanstack/react-query'
export interface ApiConfig {
analytics?: {
id: string
host: string
}
support?: {
id: string
}
silence_livekit_debug_logs?: boolean
}
const fetchConfig = (): Promise<ApiConfig> => {
return fetchApi<ApiConfig>(`config/`)
}
export const useConfig = () => {
return useQuery({
queryKey: [keys.config],
queryFn: fetchConfig,
staleTime: Infinity,
})
}

View File

@@ -0,0 +1,20 @@
import { silenceLiveKitLogs } from '@/utils/livekit'
import { useConfig } from '@/api/useConfig'
import { useAnalytics } from '@/features/analytics/hooks/useAnalytics'
import { useSupport } from '@/features/support/hooks/useSupport'
export const AppInitialization = () => {
const { data } = useConfig()
const {
analytics = {},
support = {},
silence_livekit_debug_logs = false,
} = data || {}
useAnalytics(analytics)
useSupport(support)
silenceLiveKitLogs(silence_livekit_debug_logs)
return null
}

View File

@@ -3,9 +3,6 @@ import { useLocation } from 'wouter'
import posthog from 'posthog-js' import posthog from 'posthog-js'
import { ApiUser } from '@/features/auth/api/ApiUser' import { ApiUser } from '@/features/auth/api/ApiUser'
const ANALYTICS_ID = 'phc_RPYko028Oqtj0c9exLIWwrlrjLxSdxT0ntW0Lam4iom'
const ANALYTICS_HOST = 'https://eu.i.posthog.com'
export const startAnalyticsSession = (data: ApiUser) => { export const startAnalyticsSession = (data: ApiUser) => {
if (posthog._isIdentified()) return if (posthog._isIdentified()) return
const { id, email } = data const { id, email } = data
@@ -17,21 +14,21 @@ export const terminateAnalyticsSession = () => {
posthog.reset() posthog.reset()
} }
export const useAnalytics = () => { export type useAnalyticsProps = {
id?: string
host?: string
}
export const useAnalytics = ({ id, host }: useAnalyticsProps) => {
const [location] = useLocation() const [location] = useLocation()
const isProduction = import.meta.env.PROD
useEffect(() => { useEffect(() => {
// We're on a free tier, so we need to limit the number of events we send to PostHog. if (!id || !host) return
// Be frugal with event tracking, even though we could filter them out later if necessary.
if (!isProduction) return
if (posthog.__loaded) return if (posthog.__loaded) return
posthog.init(ANALYTICS_ID, { posthog.init(id, {
api_host: ANALYTICS_HOST, api_host: host,
person_profiles: 'always', person_profiles: 'always',
}) })
}, [isProduction]) }, [id, host])
// From PostHog tutorial on PageView tracking in a Single Page Application (SPA) context. // From PostHog tutorial on PageView tracking in a Single Page Application (SPA) context.
useEffect(() => { useEffect(() => {

View File

@@ -2,8 +2,6 @@ import { useEffect } from 'react'
import { Crisp } from 'crisp-sdk-web' import { Crisp } from 'crisp-sdk-web'
import { ApiUser } from '@/features/auth/api/ApiUser' import { ApiUser } from '@/features/auth/api/ApiUser'
const SUPPORT_ID = '58ea6697-8eba-4492-bc59-ad6562585041'
export const initializeSupportSession = (user: ApiUser) => { export const initializeSupportSession = (user: ApiUser) => {
if (!Crisp.isCrispInjected()) return if (!Crisp.isCrispInjected()) return
const { id, email } = user const { id, email } = user
@@ -17,17 +15,17 @@ export const terminateSupportSession = () => {
Crisp.session.reset() Crisp.session.reset()
} }
export type useSupportProps = {
id?: string
}
// Configure Crisp chat for real-time support across all pages. // Configure Crisp chat for real-time support across all pages.
export const useSupport = () => { export const useSupport = ({ id }: useSupportProps) => {
useEffect(() => { useEffect(() => {
if (!SUPPORT_ID) { if (!id || Crisp.isCrispInjected()) return
console.warn('Crisp Website ID is not set') Crisp.configure(id)
return
}
if (Crisp.isCrispInjected()) return
Crisp.configure(SUPPORT_ID)
Crisp.setHideOnMobile(true) Crisp.setHideOnMobile(true)
}, []) }, [id])
return null return null
} }