Răsfoiți Sursa

feat(dashboard): Add TanStack Router `validateSearch` support and unauthenticated routes (#3840)

David Höck 3 luni în urmă
părinte
comite
00628dd767

+ 16 - 0
packages/dashboard/src/lib/framework/extension-api/types/navigation.ts

@@ -40,6 +40,22 @@ export interface DashboardRouteDefinition {
      * [loader function](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#route-loaders)
      */
     loader?: RouteOptions['loader'];
+
+    /**
+     * @description
+     * Optional search parameter validation function.
+     * The value is a Tanstack Router
+     * [validateSearch function](https://tanstack.com/router/latest/docs/framework/react/guide/search-params#search-param-validation)
+     */
+    validateSearch?: RouteOptions['validateSearch'];
+
+    /**
+     * @description
+     * Define if the route should be under the authentication context, i.e have the authenticated route
+     * as a parent.
+     * @default true
+     */
+    authenticated?: boolean;
 }
 
 /**

+ 59 - 17
packages/dashboard/src/lib/framework/page/use-extended-router.tsx

@@ -27,26 +27,67 @@ export const useExtendedRouter = (router: Router<AnyRoute, any, any>) => {
 
         let authenticatedRoute: AnyRoute = router.routeTree.children[authenticatedRouteIndex];
 
-        const newRoutes: AnyRoute[] = [];
+        const newAuthenticatedRoutes: AnyRoute[] = [];
+        const newRootRoutes: AnyRoute[] = [];
+
         // Create new routes for each extension
         for (const [path, config] of extensionRoutes.entries()) {
             const pathWithoutLeadingSlash = path.startsWith('/') ? path.slice(1) : path;
-            if (
-                authenticatedRoute.children.findIndex((r: AnyRoute) => r.path === pathWithoutLeadingSlash) >
-                -1
-            ) {
-                // Skip if the route already exists
-                continue;
-            }
 
-            const newRoute: AnyRoute = createRoute({
-                path: `/${pathWithoutLeadingSlash}`,
-                getParentRoute: () => authenticatedRoute,
-                loader: config.loader,
-                component: () => config.component(newRoute),
-                errorComponent: ({ error }) => <ErrorPage message={error.message} />,
-            });
-            newRoutes.push(newRoute);
+            // Check if route should be authenticated (default is true)
+            const isAuthenticated = config.authenticated !== false;
+
+            if (isAuthenticated) {
+                // Check if the route already exists under authenticated route
+                if (
+                    authenticatedRoute.children.findIndex(
+                        (r: AnyRoute) => r.path === pathWithoutLeadingSlash,
+                    ) > -1
+                ) {
+                    // Skip if the route already exists
+                    continue;
+                }
+
+                const newRoute: AnyRoute = createRoute({
+                    path: `/${pathWithoutLeadingSlash}`,
+                    getParentRoute: () => authenticatedRoute,
+                    loader: config.loader,
+                    validateSearch: config.validateSearch,
+                    component: () => config.component(newRoute),
+                    errorComponent: ({ error }) => <ErrorPage message={error.message} />,
+                });
+                newAuthenticatedRoutes.push(newRoute);
+            } else {
+                // 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(
+                        (r: AnyRoute) =>
+                            r.path === `/${pathWithoutLeadingSlash}` ||
+                            r.path === pathWithoutLeadingSlash ||
+                            r.id === `/${pathWithoutLeadingSlash}`,
+                    ) ||
+                    newRootRoutes.some(
+                        (r: AnyRoute) =>
+                            r.path === `/${pathWithoutLeadingSlash}` ||
+                            r.id === `/${pathWithoutLeadingSlash}`,
+                    );
+
+                if (routeExists) {
+                    // Skip if the route already exists
+                    continue;
+                }
+
+                const newRoute: AnyRoute = createRoute({
+                    path: `/${pathWithoutLeadingSlash}`,
+                    getParentRoute: () => router.routeTree,
+                    loader: config.loader,
+                    validateSearch: config.validateSearch,
+                    component: () => config.component(newRoute),
+                    errorComponent: ({ error }) => <ErrorPage message={error.message} />,
+                });
+                newRootRoutes.push(newRoute);
+            }
         }
 
         const childrenWithoutAuthenticated = router.routeTree.children.filter(
@@ -57,7 +98,8 @@ export const useExtendedRouter = (router: Router<AnyRoute, any, any>) => {
         const newRouter = new Router({
             routeTree: router.routeTree.addChildren([
                 ...childrenWithoutAuthenticated,
-                authenticatedRoute.addChildren([...authenticatedRoute.children, ...newRoutes]),
+                authenticatedRoute.addChildren([...authenticatedRoute.children, ...newAuthenticatedRoutes]),
+                ...newRootRoutes,
             ]),
             basepath: router.basepath,
             defaultPreload: router.options.defaultPreload,

Fișier diff suprimat deoarece este prea mare
+ 1 - 5
packages/dashboard/src/lib/graphql/graphql-env.d.ts


Fișier diff suprimat deoarece este prea mare
+ 2 - 1
packages/dev-server/graphql/graphql-env.d.ts


+ 2 - 1
packages/dev-server/test-plugins/reviews/dashboard/index.tsx

@@ -20,6 +20,7 @@ import { CustomWidget } from './custom-widget';
 import { reviewDetail } from './review-detail';
 import { reviewList } from './review-list';
 import { ReviewSelectWithCreate } from './review-select-with-create';
+import { routeWithoutAuth } from './route-without-auth';
 
 defineDashboardExtension({
     login: {
@@ -47,7 +48,7 @@ defineDashboardExtension({
             ),
         },
     },
-    routes: [reviewList, reviewDetail],
+    routes: [reviewList, reviewDetail, routeWithoutAuth],
     widgets: [
         {
             id: 'custom-widget',

+ 17 - 0
packages/dev-server/test-plugins/reviews/dashboard/route-without-auth.tsx

@@ -0,0 +1,17 @@
+import { DashboardRouteDefinition } from '@/vdb/framework/extension-api/types';
+import { z } from 'zod';
+
+const schema = z.object({
+    test: z.string(),
+    test2: z.string().optional(),
+});
+
+export const routeWithoutAuth: DashboardRouteDefinition = {
+    component: route => {
+        const search = route.useSearch();
+        return <div>Hello from without auth! Test Param from search params: {search.test}</div>;
+    },
+    validateSearch: search => schema.parse(search),
+    path: '/without-auth',
+    authenticated: false,
+};

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff