Skip to content

Commit e326249

Browse files
refactor: rework main.tsx
1 parent 6992c4a commit e326249

File tree

7 files changed

+95
-71
lines changed

7 files changed

+95
-71
lines changed

package-lock.json

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"oidc-client-ts": "^3.1.0",
3030
"react": "19.0.0",
3131
"react-dom": "19.0.0",
32+
"react-error-boundary": "^5.0.0",
3233
"react-hook-form": "^7.54.2",
3334
"react-i18next": "^15.4.1",
3435
"react-oidc-context": "^3.2.0",

public/locales/en.json

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,6 @@
118118
"EditMembers": {
119119
"addButton": "Add"
120120
},
121-
"FrontendConfigContext": {
122-
"errorMessage": "useFrontendConfig must be used within a FrontendConfigProvider"
123-
},
124121
"ControlPlaneListView": {
125122
"projectHeader": "Project:"
126123
},
@@ -141,9 +138,6 @@
141138
"App": {
142139
"loading": "Loading..."
143140
},
144-
"main": {
145-
"failedMessage": "Failed to load frontend configuration"
146-
},
147141
"Providers": {
148142
"headerProviders": "Providers",
149143
"tableHeaderVersion": "Version",
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { ReactNode, use } from 'react';
2+
import { AuthProvider, AuthProviderProps } from 'react-oidc-context';
3+
import { useFrontendConfig } from './FrontendConfigContext.tsx';
4+
import { LoadCrateKubeConfig } from '../lib/oidc/crate.ts';
5+
6+
interface AuthProviderOnboardingProps {
7+
children?: ReactNode;
8+
}
9+
10+
// Promise needs to be cached
11+
// https://react.dev/blog/2024/12/05/react-19#use-does-not-support-promises-created-in-render
12+
const fetchAuthPromiseCache = new Map<string, Promise<AuthProviderProps>>();
13+
14+
export function AuthProviderOnboarding({
15+
children,
16+
}: AuthProviderOnboardingProps) {
17+
const { backendUrl } = useFrontendConfig();
18+
19+
const fetchAuthConfigPromise =
20+
fetchAuthPromiseCache.get(backendUrl) ?? LoadCrateKubeConfig(backendUrl);
21+
fetchAuthPromiseCache.set(backendUrl, fetchAuthConfigPromise);
22+
23+
const authConfig = use(fetchAuthConfigPromise);
24+
25+
return <AuthProvider {...authConfig}>{children}</AuthProvider>;
26+
}
Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { FC, ReactNode, createContext, useContext } from 'react';
1+
import { ReactNode, createContext, use } from 'react';
22
import { DocLinkCreator } from '../lib/shared/links';
3-
import { useTranslation } from 'react-i18next';
43

54
export enum Landscape {
65
Live = 'LIVE',
@@ -9,49 +8,47 @@ export enum Landscape {
98
Development = 'DEV',
109
}
1110

12-
export interface FrontendConfig {
11+
interface FrontendConfigContextProps {
1312
backendUrl: string;
1413
landscape?: Landscape;
1514
documentationBaseUrl: string;
16-
}
17-
18-
export interface FrontendConfigProviderProps extends FrontendConfig {
1915
links: DocLinkCreator;
2016
}
2117

22-
const FrontendConfigContext = createContext<FrontendConfigProviderProps | null>(
18+
const FrontendConfigContext = createContext<FrontendConfigContextProps | null>(
2319
null,
2420
);
2521

26-
export const useFrontendConfig = () => {
27-
const c = useContext(FrontendConfigContext);
28-
const { t } = useTranslation();
29-
30-
if (!c) {
31-
throw new Error(t('FrontendConfigContext.errorMessage'));
32-
}
33-
return c;
34-
};
22+
const fetchPromise = fetch('/frontend-config.json').then((res) => res.json());
3523

36-
export const FrontendConfigProvider: FC<{
24+
interface FrontendConfigProviderProps {
3725
children: ReactNode;
38-
config: FrontendConfig;
39-
}> = ({ children, config }) => {
26+
}
27+
28+
export function FrontendConfigProvider({
29+
children,
30+
}: FrontendConfigProviderProps) {
31+
const config = use(fetchPromise);
4032
const docLinks = new DocLinkCreator(config.documentationBaseUrl);
33+
const value: FrontendConfigContextProps = {
34+
links: docLinks,
35+
backendUrl: config.backendUrl,
36+
landscape: config.landscape,
37+
documentationBaseUrl: config.documentationBaseUrl,
38+
};
39+
4140
return (
42-
<FrontendConfigContext.Provider
43-
value={{
44-
links: docLinks,
45-
backendUrl: config.backendUrl,
46-
landscape: config.landscape,
47-
documentationBaseUrl: config.documentationBaseUrl,
48-
}}
49-
>
50-
{children}
51-
</FrontendConfigContext.Provider>
41+
<FrontendConfigContext value={value}>{children}</FrontendConfigContext>
5242
);
53-
};
54-
55-
export async function LoadFrontendConfig(): Promise<FrontendConfig> {
56-
return fetch('/frontend-config.json').then((res) => res.json());
5743
}
44+
45+
export const useFrontendConfig = () => {
46+
const context = use(FrontendConfigContext);
47+
48+
if (!context) {
49+
throw new Error(
50+
'useFrontendConfig must be used within a FrontendConfigProvider.',
51+
);
52+
}
53+
return context;
54+
};

src/lib/oidc/crate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { GetAuthPropsForCurrentContext } from './shared.ts';
33

44
export function LoadCrateKubeConfig(
55
backendUrl: string,
6-
): Promise<AuthProviderProps | void> {
6+
): Promise<AuthProviderProps> {
77
const uri = backendUrl + '/.well-known/openmcp/kubeconfig';
88

99
return fetch(uri)

src/main.tsx

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,33 @@
1-
import React from 'react';
2-
import ReactDOM from 'react-dom/client';
1+
import React, { Suspense } from 'react';
2+
import { createRoot } from 'react-dom/client';
33
import './index.css';
44
import App from './App';
5-
import { ThemeProvider } from '@ui5/webcomponents-react';
6-
import { AuthProvider } from 'react-oidc-context';
7-
import { LoadCrateKubeConfig } from './lib/oidc/crate.ts';
5+
import { BusyIndicator, ThemeProvider } from '@ui5/webcomponents-react';
86
import { SWRConfig } from 'swr';
97
import { ToastProvider } from './context/ToastContext.tsx';
108
import { CopyButtonProvider } from './context/CopyButtonContext.tsx';
11-
import {
12-
FrontendConfigProvider,
13-
LoadFrontendConfig,
14-
} from './context/FrontendConfigContext.tsx';
9+
import { FrontendConfigProvider } from './context/FrontendConfigContext.tsx';
1510
import '@ui5/webcomponents-react/dist/Assets'; //used for loading themes
1611
import { DarkModeSystemSwitcher } from './components/Core/DarkModeSystemSwitcher.tsx';
1712
import '.././i18n.ts';
1813
import './utils/i18n/timeAgo';
19-
import { useTranslation } from 'react-i18next';
14+
import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
15+
import IllustratedError from './components/Shared/IllustratedError.tsx';
16+
import { AuthProviderOnboarding } from './context/AuthProviderOnboarding.tsx';
2017

21-
(async () => {
22-
try {
23-
const frontendConfig = await LoadFrontendConfig();
24-
const authconfig = await LoadCrateKubeConfig(frontendConfig.backendUrl);
18+
const ErrorFallback = ({ error }: FallbackProps) => {
19+
return <IllustratedError error={error} />;
20+
};
2521

26-
ReactDOM.createRoot(document.getElementById('root')!).render(
27-
<React.StrictMode>
28-
<FrontendConfigProvider config={frontendConfig}>
29-
<AuthProvider key={'crate'} {...authconfig}>
22+
const rootElement = document.getElementById('root');
23+
const root = createRoot(rootElement!);
24+
25+
root.render(
26+
<React.StrictMode>
27+
<ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => {}}>
28+
<Suspense fallback={<BusyIndicator active />}>
29+
<FrontendConfigProvider>
30+
<AuthProviderOnboarding>
3031
<ThemeProvider>
3132
<ToastProvider>
3233
<CopyButtonProvider>
@@ -41,17 +42,9 @@ import { useTranslation } from 'react-i18next';
4142
</CopyButtonProvider>
4243
</ToastProvider>
4344
</ThemeProvider>
44-
</AuthProvider>
45+
</AuthProviderOnboarding>
4546
</FrontendConfigProvider>
46-
</React.StrictMode>,
47-
);
48-
} catch (e) {
49-
const { t } = useTranslation();
50-
console.error('failed to load frontend configuration or kubeconfig', e);
51-
ReactDOM.createRoot(document.getElementById('root')!).render(
52-
<React.StrictMode>
53-
<div>{t('main.failedMessage')}=</div>
54-
</React.StrictMode>,
55-
);
56-
}
57-
})();
47+
</Suspense>
48+
</ErrorBoundary>
49+
</React.StrictMode>,
50+
);

0 commit comments

Comments
 (0)