فهرست منبع

fix(dashboard): Fix router initialization and BASE_URL processing (#3841)

David Höck 3 ماه پیش
والد
کامیت
f34e855ceb
2فایلهای تغییر یافته به همراه76 افزوده شده و 38 حذف شده
  1. 28 15
      packages/dashboard/src/app/main.tsx
  2. 48 23
      packages/dashboard/src/lib/framework/page/use-extended-router.tsx

+ 28 - 15
packages/dashboard/src/app/main.tsx

@@ -7,7 +7,7 @@ import { useExtendedRouter } from '@/vdb/framework/page/use-extended-router.js';
 import { useAuth } from '@/vdb/hooks/use-auth.js';
 import { useServerConfig } from '@/vdb/hooks/use-server-config.js';
 import { defaultLocale, dynamicActivate } from '@/vdb/providers/i18n-provider.js';
-import { createRouter, RouterProvider } from '@tanstack/react-router';
+import { AnyRoute, createRouter, RouterOptions, RouterProvider } from '@tanstack/react-router';
 import React, { useEffect } from 'react';
 import ReactDOM from 'react-dom/client';
 
@@ -15,30 +15,43 @@ import { AppProviders, queryClient } from './app-providers.js';
 import { routeTree } from './routeTree.gen.js';
 import './styles.css';
 
-// Register things for typesafety
-declare module '@tanstack/react-router' {
-    interface Register {
-        router: typeof router;
-    }
-}
+const processedBaseUrl = (() => {
+    const baseUrl = import.meta.env.BASE_URL;
+    if (!baseUrl || baseUrl === '/') return undefined;
+    // Ensure leading slash, remove trailing slash
+    const normalized = baseUrl.startsWith('/') ? baseUrl : '/' + baseUrl;
+    return normalized.endsWith('/') ? normalized.slice(0, -1) : normalized;
+})();
 
-export const router = createRouter({
-    routeTree,
-    defaultPreload: 'intent',
+const routerOptions: RouterOptions<AnyRoute, any> = {
+    defaultPreload: 'intent' as const,
     scrollRestoration: true,
-    // In case the dashboard gets served from a subpath, we need to set the basepath based on the environment variable
-    ...(import.meta.env.BASE_URL ? { basepath: import.meta.env.BASE_URL } : {}),
+    basepath: processedBaseUrl,
     context: {
         /* eslint-disable @typescript-eslint/no-non-null-assertion */
         auth: undefined!, // This will be set after we wrap the app in an AuthProvider
         queryClient,
     },
     defaultErrorComponent: ({ error }: { error: Error }) => <div>Uh Oh!!! {error.message}</div>,
+};
+
+// Create a type-only router instance for TypeScript type registration
+// The actual runtime router is created in InnerApp component
+const typeRouter = createRouter({
+    ...routerOptions,
+    routeTree,
 });
 
+// Register the router type for TypeScript
+declare module '@tanstack/react-router' {
+    interface Register {
+        router: typeof typeRouter;
+    }
+}
+
 function InnerApp() {
     const auth = useAuth();
-    const extendedRouter = useExtendedRouter(router);
+    const router = useExtendedRouter(routeTree, routerOptions);
     const serverConfig = useServerConfig();
     const [hasSetCustomFieldsMap, setHasSetCustomFieldsMap] = React.useState(false);
 
@@ -53,7 +66,7 @@ function InnerApp() {
     return (
         <>
             {(hasSetCustomFieldsMap || auth.status === 'unauthenticated') && (
-                <RouterProvider router={extendedRouter} context={{ auth, queryClient }} />
+                <RouterProvider router={router} context={{ auth, queryClient }} />
             )}
         </>
     );
@@ -64,7 +77,7 @@ function App() {
     const { extensionsLoaded } = useDashboardExtensions();
     useEffect(() => {
         // With this method we dynamically load the catalogs
-        dynamicActivate(defaultLocale, () => {
+        void dynamicActivate(defaultLocale, () => {
             setI18nLoaded(true);
         });
         registerDefaults();

+ 48 - 23
packages/dashboard/src/lib/framework/page/use-extended-router.tsx

@@ -1,4 +1,4 @@
-import { AnyRoute, createRoute, Router } from '@tanstack/react-router';
+import { AnyRoute, createRoute, createRouter, RouterOptions } from '@tanstack/react-router';
 import { useMemo } from 'react';
 import { ErrorPage } from '../../components/shared/error-page.js';
 import { AUTHENTICATED_ROUTE_PREFIX } from '../../constants.js';
@@ -6,26 +6,40 @@ import { useDashboardExtensions } from '../extension-api/use-dashboard-extension
 import { extensionRoutes } from './page-api.js';
 
 /**
- * Extends the TanStack Router with additional routes for each dashboard
- * extension.
+ * Creates a TanStack Router with the base route tree extended with additional
+ * routes from dashboard extensions.
  */
-export const useExtendedRouter = (router: Router<AnyRoute, any, any>) => {
+export const useExtendedRouter = (
+    baseRouteTree: AnyRoute,
+    routerOptions: Omit<RouterOptions<AnyRoute, any>, 'routeTree'>,
+) => {
     const { extensionsLoaded } = useDashboardExtensions();
 
     return useMemo(() => {
+        // Start with the base route tree
+        let routeTree = baseRouteTree;
+
+        // Only extend if extensions are loaded
         if (!extensionsLoaded) {
-            return router;
+            return createRouter({
+                ...routerOptions,
+                routeTree,
+            });
         }
 
-        const authenticatedRouteIndex = router.routeTree.children.findIndex(
+        const authenticatedRouteIndex = routeTree.children.findIndex(
             (r: AnyRoute) => r.id === AUTHENTICATED_ROUTE_PREFIX,
         );
 
         if (authenticatedRouteIndex === -1) {
-            return router;
+            // No authenticated route found, return router with base tree
+            return createRouter({
+                ...routerOptions,
+                routeTree,
+            });
         }
 
-        let authenticatedRoute: AnyRoute = router.routeTree.children[authenticatedRouteIndex];
+        let authenticatedRoute: AnyRoute = routeTree.children[authenticatedRouteIndex];
 
         const newAuthenticatedRoutes: AnyRoute[] = [];
         const newRootRoutes: AnyRoute[] = [];
@@ -61,7 +75,7 @@ export const useExtendedRouter = (router: Router<AnyRoute, any, any>) => {
                 // Check if the route already exists at the root level
                 // Check both by path and by id (which includes the leading slash)
                 const routeExists =
-                    router.routeTree.children.some(
+                    routeTree.children.some(
                         (r: AnyRoute) =>
                             r.path === `/${pathWithoutLeadingSlash}` ||
                             r.path === pathWithoutLeadingSlash ||
@@ -80,7 +94,7 @@ export const useExtendedRouter = (router: Router<AnyRoute, any, any>) => {
 
                 const newRoute: AnyRoute = createRoute({
                     path: `/${pathWithoutLeadingSlash}`,
-                    getParentRoute: () => router.routeTree,
+                    getParentRoute: () => routeTree,
                     loader: config.loader,
                     validateSearch: config.validateSearch,
                     component: () => config.component(newRoute),
@@ -90,21 +104,32 @@ export const useExtendedRouter = (router: Router<AnyRoute, any, any>) => {
             }
         }
 
-        const childrenWithoutAuthenticated = router.routeTree.children.filter(
+        // Only extend the tree if we have new routes to add
+        if (newAuthenticatedRoutes.length === 0 && newRootRoutes.length === 0) {
+            return createRouter({
+                ...routerOptions,
+                routeTree,
+            });
+        }
+
+        const childrenWithoutAuthenticated = routeTree.children.filter(
             (r: AnyRoute) => r.id !== AUTHENTICATED_ROUTE_PREFIX,
         );
 
-        // Create a new router with the modified route tree
-        const newRouter = new Router({
-            routeTree: router.routeTree.addChildren([
-                ...childrenWithoutAuthenticated,
-                authenticatedRoute.addChildren([...authenticatedRoute.children, ...newAuthenticatedRoutes]),
-                ...newRootRoutes,
-            ]),
-            basepath: router.basepath,
-            defaultPreload: router.options.defaultPreload,
-            defaultPreloadDelay: router.options.defaultPreloadDelay,
+        const updatedAuthenticatedRoute = authenticatedRoute.addChildren([
+            ...authenticatedRoute.children,
+            ...newAuthenticatedRoutes,
+        ]);
+
+        const extendedRouteTree: AnyRoute = routeTree.addChildren([
+            ...childrenWithoutAuthenticated,
+            updatedAuthenticatedRoute,
+            ...newRootRoutes,
+        ]);
+
+        return createRouter({
+            ...routerOptions,
+            routeTree: extendedRouteTree,
         });
-        return newRouter;
-    }, [router, extensionsLoaded]);
+    }, [baseRouteTree, routerOptions, extensionsLoaded]);
 };