create-better-t-stack 2.7.2 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/index.js +17 -17
  2. package/package.json +1 -1
  3. package/templates/api/orpc/web/solid/src/utils/orpc.ts.hbs +30 -0
  4. package/templates/auth/server/base/src/lib/auth.ts.hbs +1 -1
  5. package/templates/auth/web/solid/src/components/sign-in-form.tsx +132 -0
  6. package/templates/auth/web/solid/src/components/sign-up-form.tsx +158 -0
  7. package/templates/auth/web/solid/src/components/user-menu.tsx +54 -0
  8. package/templates/auth/web/solid/src/lib/auth-client.ts +5 -0
  9. package/templates/auth/web/solid/src/routes/dashboard.tsx +38 -0
  10. package/templates/auth/web/solid/src/routes/login.tsx +23 -0
  11. package/templates/backend/server/express/src/index.ts.hbs +2 -2
  12. package/templates/db/prisma/sqlite/prisma/schema/schema.prisma +1 -1
  13. package/templates/examples/todo/web/solid/src/routes/todos.tsx +132 -0
  14. package/templates/frontend/react/react-router/vite.config.ts.hbs +3 -7
  15. package/templates/frontend/react/tanstack-router/vite.config.ts.hbs +3 -5
  16. package/templates/frontend/solid/_gitignore +7 -0
  17. package/templates/frontend/solid/index.html +13 -0
  18. package/templates/frontend/solid/package.json +33 -0
  19. package/templates/frontend/solid/public/robots.txt +3 -0
  20. package/templates/frontend/solid/src/components/header.tsx.hbs +38 -0
  21. package/templates/frontend/solid/src/components/loader.tsx +9 -0
  22. package/templates/frontend/solid/src/main.tsx.hbs +32 -0
  23. package/templates/frontend/solid/src/routes/__root.tsx.hbs +21 -0
  24. package/templates/frontend/solid/src/routes/index.tsx.hbs +65 -0
  25. package/templates/frontend/solid/src/routes/todos.tsx +132 -0
  26. package/templates/frontend/solid/src/styles.css +5 -0
  27. package/templates/frontend/solid/tsconfig.json +29 -0
  28. package/templates/frontend/solid/vite.config.js.hbs +39 -0
@@ -0,0 +1,158 @@
1
+ import { authClient } from "@/lib/auth-client";
2
+ import { createForm } from "@tanstack/solid-form";
3
+ import { useNavigate } from "@tanstack/solid-router";
4
+ import { z } from "zod";
5
+ import { For } from "solid-js";
6
+
7
+ export default function SignUpForm({
8
+ onSwitchToSignIn,
9
+ }: {
10
+ onSwitchToSignIn: () => void;
11
+ }) {
12
+ const navigate = useNavigate({
13
+ from: "/",
14
+ });
15
+
16
+ const form = createForm(() => ({
17
+ defaultValues: {
18
+ email: "",
19
+ password: "",
20
+ name: "",
21
+ },
22
+ onSubmit: async ({ value }) => {
23
+ await authClient.signUp.email(
24
+ {
25
+ email: value.email,
26
+ password: value.password,
27
+ name: value.name,
28
+ },
29
+ {
30
+ onSuccess: () => {
31
+ navigate({
32
+ to: "/dashboard",
33
+ });
34
+ console.log("Sign up successful");
35
+ },
36
+ onError: (error) => {
37
+ console.error(error.error.message);
38
+ },
39
+ },
40
+ );
41
+ },
42
+ validators: {
43
+ onSubmit: z.object({
44
+ name: z.string().min(2, "Name must be at least 2 characters"),
45
+ email: z.string().email("Invalid email address"),
46
+ password: z.string().min(8, "Password must be at least 8 characters"),
47
+ }),
48
+ },
49
+ }));
50
+
51
+ return (
52
+ <div class="mx-auto w-full mt-10 max-w-md p-6">
53
+ <h1 class="mb-6 text-center text-3xl font-bold">Create Account</h1>
54
+
55
+ <form
56
+ onSubmit={(e) => {
57
+ e.preventDefault();
58
+ e.stopPropagation();
59
+ void form.handleSubmit();
60
+ }}
61
+ class="space-y-4"
62
+ >
63
+ <div>
64
+ <form.Field name="name">
65
+ {(field) => (
66
+ <div class="space-y-2">
67
+ <label for={field().name}>Name</label>
68
+ <input
69
+ id={field().name}
70
+ name={field().name}
71
+ value={field().state.value}
72
+ onBlur={field().handleBlur}
73
+ onInput={(e) => field().handleChange(e.currentTarget.value)}
74
+ class="w-full rounded border p-2"
75
+ />
76
+ <For each={field().state.meta.errors}>
77
+ {(error) => (
78
+ <p class="text-sm text-red-600">{error?.message}</p>
79
+ )}
80
+ </For>
81
+ </div>
82
+ )}
83
+ </form.Field>
84
+ </div>
85
+
86
+ <div>
87
+ <form.Field name="email">
88
+ {(field) => (
89
+ <div class="space-y-2">
90
+ <label for={field().name}>Email</label>
91
+ <input
92
+ id={field().name}
93
+ name={field().name}
94
+ type="email"
95
+ value={field().state.value}
96
+ onBlur={field().handleBlur}
97
+ onInput={(e) => field().handleChange(e.currentTarget.value)}
98
+ class="w-full rounded border p-2"
99
+ />
100
+ <For each={field().state.meta.errors}>
101
+ {(error) => (
102
+ <p class="text-sm text-red-600">{error?.message}</p>
103
+ )}
104
+ </For>
105
+ </div>
106
+ )}
107
+ </form.Field>
108
+ </div>
109
+
110
+ <div>
111
+ <form.Field name="password">
112
+ {(field) => (
113
+ <div class="space-y-2">
114
+ <label for={field().name}>Password</label>
115
+ <input
116
+ id={field().name}
117
+ name={field().name}
118
+ type="password"
119
+ value={field().state.value}
120
+ onBlur={field().handleBlur}
121
+ onInput={(e) => field().handleChange(e.currentTarget.value)}
122
+ class="w-full rounded border p-2"
123
+ />
124
+ <For each={field().state.meta.errors}>
125
+ {(error) => (
126
+ <p class="text-sm text-red-600">{error?.message}</p>
127
+ )}
128
+ </For>
129
+ </div>
130
+ )}
131
+ </form.Field>
132
+ </div>
133
+
134
+ <form.Subscribe>
135
+ {(state) => (
136
+ <button
137
+ type="submit"
138
+ class="w-full rounded bg-indigo-600 p-2 text-white hover:bg-indigo-700 disabled:opacity-50"
139
+ disabled={!state().canSubmit || state().isSubmitting}
140
+ >
141
+ {state().isSubmitting ? "Submitting..." : "Sign Up"}
142
+ </button>
143
+ )}
144
+ </form.Subscribe>
145
+ </form>
146
+
147
+ <div class="mt-4 text-center">
148
+ <button
149
+ type="button"
150
+ onClick={onSwitchToSignIn}
151
+ class="text-sm text-indigo-600 hover:text-indigo-800 hover:underline"
152
+ >
153
+ Already have an account? Sign In
154
+ </button>
155
+ </div>
156
+ </div>
157
+ );
158
+ }
@@ -0,0 +1,54 @@
1
+ import { authClient } from "@/lib/auth-client";
2
+ import { useNavigate, Link } from "@tanstack/solid-router";
3
+ import { createSignal, Show } from "solid-js";
4
+
5
+ export default function UserMenu() {
6
+ const navigate = useNavigate();
7
+ const session = authClient.useSession();
8
+ const [isMenuOpen, setIsMenuOpen] = createSignal(false);
9
+
10
+ return (
11
+ <div class="relative inline-block text-left">
12
+ <Show when={session().isPending}>
13
+ <div class="h-9 w-24 animate-pulse rounded" />
14
+ </Show>
15
+
16
+ <Show when={!session().isPending && !session().data}>
17
+ <Link to="/login" class="inline-block border rounded px-4 text-sm">
18
+ Sign In
19
+ </Link>
20
+ </Show>
21
+
22
+ <Show when={!session().isPending && session().data}>
23
+ <button
24
+ type="button"
25
+ class="inline-block border rounded px-4 text-sm"
26
+ onClick={() => setIsMenuOpen(!isMenuOpen())}
27
+ >
28
+ {session().data?.user.name}
29
+ </button>
30
+
31
+ <Show when={isMenuOpen()}>
32
+ <div class="absolute right-0 mt-2 w-56 rounded p-1 shadow-sm">
33
+ <div class="px-4 text-sm">{session().data?.user.email}</div>
34
+ <button
35
+ class="mt-1 w-full border rounded px-4 text-center text-sm"
36
+ onClick={() => {
37
+ setIsMenuOpen(false);
38
+ authClient.signOut({
39
+ fetchOptions: {
40
+ onSuccess: () => {
41
+ navigate({ to: "/" });
42
+ },
43
+ },
44
+ });
45
+ }}
46
+ >
47
+ Sign Out
48
+ </button>
49
+ </div>
50
+ </Show>
51
+ </Show>
52
+ </div>
53
+ );
54
+ }
@@ -0,0 +1,5 @@
1
+ import { createAuthClient } from "better-auth/solid";
2
+
3
+ export const authClient = createAuthClient({
4
+ baseURL: import.meta.env.VITE_SERVER_URL,
5
+ });
@@ -0,0 +1,38 @@
1
+ import { authClient } from "@/lib/auth-client";
2
+ import { orpc } from "@/utils/orpc";
3
+ import { useQuery } from "@tanstack/solid-query";
4
+ import { createFileRoute } from "@tanstack/solid-router";
5
+ import { createEffect, Show } from "solid-js";
6
+
7
+ export const Route = createFileRoute("/dashboard")({
8
+ component: RouteComponent,
9
+ });
10
+
11
+ function RouteComponent() {
12
+ const session = authClient.useSession();
13
+ const navigate = Route.useNavigate();
14
+
15
+ const privateData = useQuery(() => orpc.privateData.queryOptions());
16
+
17
+ createEffect(() => {
18
+ if (!session().data && !session().isPending) {
19
+ navigate({
20
+ to: "/login",
21
+ });
22
+ }
23
+ });
24
+
25
+ return (
26
+ <div>
27
+ <Show when={session().isPending}>
28
+ <div>Loading...</div>
29
+ </Show>
30
+
31
+ <Show when={!session().isPending && session().data}>
32
+ <h1>Dashboard</h1>
33
+ <p>Welcome {session().data?.user.name}</p>
34
+ <p>privateData: {privateData.data?.message}</p>
35
+ </Show>
36
+ </div>
37
+ );
38
+ }
@@ -0,0 +1,23 @@
1
+ import SignInForm from "@/components/sign-in-form";
2
+ import SignUpForm from "@/components/sign-up-form";
3
+ import { createFileRoute } from "@tanstack/solid-router";
4
+ import { createSignal, Match, Switch } from "solid-js";
5
+
6
+ export const Route = createFileRoute("/login")({
7
+ component: RouteComponent,
8
+ });
9
+
10
+ function RouteComponent() {
11
+ const [showSignIn, setShowSignIn] = createSignal(false);
12
+
13
+ return (
14
+ <Switch>
15
+ <Match when={showSignIn()}>
16
+ <SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />
17
+ </Match>
18
+ <Match when={!showSignIn()}>
19
+ <SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />
20
+ </Match>
21
+ </Switch>
22
+ );
23
+ }
@@ -39,8 +39,6 @@ app.use(
39
39
  app.all("/api/auth{/*path}", toNodeHandler(auth));
40
40
  {{/if}}
41
41
 
42
- app.use(express.json())
43
-
44
42
  {{#if (eq api "trpc")}}
45
43
  app.use(
46
44
  "/trpc",
@@ -67,6 +65,8 @@ app.use('/rpc{*path}', async (req, res, next) => {
67
65
  });
68
66
  {{/if}}
69
67
 
68
+ app.use(express.json())
69
+
70
70
  {{#if (includes examples "ai")}}
71
71
  app.post("/ai", async (req, res) => {
72
72
  const { messages = [] } = req.body || {};
@@ -6,5 +6,5 @@ generator client {
6
6
 
7
7
  datasource db {
8
8
  provider = "sqlite"
9
- url = "file:../local.db"
9
+ url = env("DATABASE_URL")
10
10
  }
@@ -0,0 +1,132 @@
1
+ import { createFileRoute } from "@tanstack/solid-router";
2
+ import { Loader2, Trash2 } from "lucide-solid";
3
+ import { createSignal, For, Show } from "solid-js";
4
+ import { orpc } from "@/utils/orpc";
5
+ import { useQuery, useMutation } from "@tanstack/solid-query";
6
+
7
+ export const Route = createFileRoute("/todos")({
8
+ component: TodosRoute,
9
+ });
10
+
11
+ function TodosRoute() {
12
+ const [newTodoText, setNewTodoText] = createSignal("");
13
+
14
+ const todos = useQuery(() => orpc.todo.getAll.queryOptions());
15
+
16
+ const createMutation = useMutation(() =>
17
+ orpc.todo.create.mutationOptions({
18
+ onSuccess: () => {
19
+ todos.refetch();
20
+ setNewTodoText("");
21
+ },
22
+ }),
23
+ );
24
+
25
+ const toggleMutation = useMutation(() =>
26
+ orpc.todo.toggle.mutationOptions({
27
+ onSuccess: () => todos.refetch(),
28
+ }),
29
+ );
30
+
31
+ const deleteMutation = useMutation(() =>
32
+ orpc.todo.delete.mutationOptions({
33
+ onSuccess: () => todos.refetch(),
34
+ }),
35
+ );
36
+
37
+ const handleAddTodo = (e: Event) => {
38
+ e.preventDefault();
39
+ if (newTodoText().trim()) {
40
+ createMutation.mutate({ text: newTodoText() });
41
+ }
42
+ };
43
+
44
+ const handleToggleTodo = (id: number, completed: boolean) => {
45
+ toggleMutation.mutate({ id, completed: !completed });
46
+ };
47
+
48
+ const handleDeleteTodo = (id: number) => {
49
+ deleteMutation.mutate({ id });
50
+ };
51
+
52
+ return (
53
+ <div class="mx-auto w-full max-w-md py-10">
54
+ <div class="rounded-lg border p-6 shadow-sm">
55
+ <div class="mb-4">
56
+ <h2 class="text-xl font-semibold">Todo List</h2>
57
+ <p class="text-sm">Manage your tasks efficiently</p>
58
+ </div>
59
+ <div>
60
+ <form
61
+ onSubmit={handleAddTodo}
62
+ class="mb-6 flex items-center space-x-2"
63
+ >
64
+ <input
65
+ type="text"
66
+ value={newTodoText()}
67
+ onInput={(e) => setNewTodoText(e.currentTarget.value)}
68
+ placeholder="Add a new task..."
69
+ disabled={createMutation.isPending}
70
+ class="w-full rounded-md border p-2 text-sm"
71
+ />
72
+ <button
73
+ type="submit"
74
+ disabled={createMutation.isPending || !newTodoText().trim()}
75
+ class="rounded-md bg-blue-600 px-4 py-2 text-sm text-white disabled:opacity-50"
76
+ >
77
+ <Show when={createMutation.isPending} fallback="Add">
78
+ <Loader2 class="h-4 w-4 animate-spin" />
79
+ </Show>
80
+ </button>
81
+ </form>
82
+
83
+ <Show when={todos.isLoading}>
84
+ <div class="flex justify-center py-4">
85
+ <Loader2 class="h-6 w-6 animate-spin" />
86
+ </div>
87
+ </Show>
88
+
89
+ <Show when={!todos.isLoading && todos.data?.length === 0}>
90
+ <p class="py-4 text-center">No todos yet. Add one above!</p>
91
+ </Show>
92
+
93
+ <Show when={!todos.isLoading}>
94
+ <ul class="space-y-2">
95
+ <For each={todos.data}>
96
+ {(todo) => (
97
+ <li class="flex items-center justify-between rounded-md border p-2">
98
+ <div class="flex items-center space-x-2">
99
+ <input
100
+ type="checkbox"
101
+ checked={todo.completed}
102
+ onChange={() =>
103
+ handleToggleTodo(todo.id, todo.completed)
104
+ }
105
+ id={`todo-${todo.id}`}
106
+ class="h-4 w-4"
107
+ />
108
+ <label
109
+ for={`todo-${todo.id}`}
110
+ class={todo.completed ? "line-through" : ""}
111
+ >
112
+ {todo.text}
113
+ </label>
114
+ </div>
115
+ <button
116
+ type="button"
117
+ onClick={() => handleDeleteTodo(todo.id)}
118
+ aria-label="Delete todo"
119
+ class="ml-2 rounded-md p-1"
120
+ >
121
+ <Trash2 class="h-4 w-4" />
122
+ </button>
123
+ </li>
124
+ )}
125
+ </For>
126
+ </ul>
127
+ </Show>
128
+ </div>
129
+ </div>
130
+ </div>
131
+ );
132
+ }
@@ -1,4 +1,3 @@
1
- {{! Import VitePWA only if 'pwa' addon is selected }}
2
1
  {{#if (includes addons "pwa")}}
3
2
  import { VitePWA } from "vite-plugin-pwa";
4
3
  {{/if}}
@@ -12,24 +11,21 @@ export default defineConfig({
12
11
  tailwindcss(),
13
12
  reactRouter(),
14
13
  tsconfigPaths(),
15
- {{! Add VitePWA plugin config only if 'pwa' addon is selected }}
16
14
  {{#if (includes addons "pwa")}}
17
15
  VitePWA({
18
16
  registerType: "autoUpdate",
19
17
  manifest: {
20
- // Use context variables for better naming
21
18
  name: "{{projectName}}",
22
19
  short_name: "{{projectName}}",
23
20
  description: "{{projectName}} - PWA Application",
24
21
  theme_color: "#0c0c0c",
25
- // Add more manifest options as needed
26
22
  },
27
23
  pwaAssets: {
28
- disabled: false, // Set to false to enable asset generation
29
- config: true, // Use pwa-assets.config.ts
24
+ disabled: false,
25
+ config: true,
30
26
  },
31
27
  devOptions: {
32
- enabled: true, // Enable PWA features in dev mode
28
+ enabled: true,
33
29
  },
34
30
  }),
35
31
  {{/if}}
@@ -16,19 +16,17 @@ export default defineConfig({
16
16
  VitePWA({
17
17
  registerType: "autoUpdate",
18
18
  manifest: {
19
- // Use context variables for better naming
20
19
  name: "{{projectName}}",
21
20
  short_name: "{{projectName}}",
22
21
  description: "{{projectName}} - PWA Application",
23
22
  theme_color: "#0c0c0c",
24
- // Add more manifest options as needed
25
23
  },
26
24
  pwaAssets: {
27
- disabled: false, // Set to false to enable asset generation
28
- config: true, // Use pwa-assets.config.ts
25
+ disabled: false,
26
+ config: true,
29
27
  },
30
28
  devOptions: {
31
- enabled: true, // Enable PWA features in dev mode
29
+ enabled: true,
32
30
  },
33
31
  }),
34
32
  {{/if}}
@@ -0,0 +1,7 @@
1
+ node_modules
2
+ .DS_Store
3
+ dist
4
+ dist-ssr
5
+ *.local
6
+ .env
7
+ .env.*
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <link rel="icon" href="/favicon.ico" />
7
+ <meta name="theme-color" content="#000000" />
8
+ </head>
9
+ <body>
10
+ <div id="app"></div>
11
+ <script type="module" src="/src/main.tsx"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "web",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "vite --port 3001",
7
+ "build": "vite build && tsc",
8
+ "serve": "vite preview",
9
+ "test": "vitest run"
10
+ },
11
+ "dependencies": {
12
+ "@orpc/client": "^1.1.1",
13
+ "@orpc/server": "^1.1.1",
14
+ "@orpc/solid-query": "^1.1.1",
15
+ "@tailwindcss/vite": "^4.0.6",
16
+ "@tanstack/router-plugin": "^1.109.2",
17
+ "@tanstack/solid-form": "^1.9.0",
18
+ "@tanstack/solid-query": "^5.75.0",
19
+ "@tanstack/solid-query-devtools": "^5.75.0",
20
+ "@tanstack/solid-router": "^1.110.0",
21
+ "@tanstack/solid-router-devtools": "^1.109.2",
22
+ "better-auth": "^1.2.7",
23
+ "lucide-solid": "^0.507.0",
24
+ "solid-js": "^1.9.4",
25
+ "tailwindcss": "^4.0.6",
26
+ "zod": "^3.24.3"
27
+ },
28
+ "devDependencies": {
29
+ "typescript": "^5.7.2",
30
+ "vite": "^6.0.11",
31
+ "vite-plugin-solid": "^2.11.2"
32
+ }
33
+ }
@@ -0,0 +1,3 @@
1
+ # https://www.robotstxt.org/robotstxt.html
2
+ User-agent: *
3
+ Disallow:
@@ -0,0 +1,38 @@
1
+ import { Link } from "@tanstack/solid-router";
2
+ {{#if auth}}
3
+ import UserMenu from "./user-menu";
4
+ {{/if}}
5
+ import { For } from "solid-js";
6
+
7
+ export default function Header() {
8
+ const links = [
9
+ { to: "/", label: "Home" },
10
+ {{#if auth}}
11
+ { to: "/dashboard", label: "Dashboard" },
12
+ {{/if}}
13
+ {{#if (includes examples "todo")}}
14
+ { to: "/todos", label: "Todos" },
15
+ {{/if}}
16
+ {{#if (includes examples "ai")}}
17
+ { to: "/ai", label: "AI Chat" },
18
+ {{/if}}
19
+ ];
20
+
21
+ return (
22
+ <div>
23
+ <div class="flex flex-row items-center justify-between px-2 py-1">
24
+ <nav class="flex gap-4 text-lg">
25
+ <For each={links}>
26
+ {(link) => <Link to={link.to}>{link.label}</Link>}
27
+ </For>
28
+ </nav>
29
+ <div class="flex items-center gap-2">
30
+ {{#if auth}}
31
+ <UserMenu />
32
+ {{/if}}
33
+ </div>
34
+ </div>
35
+ <hr />
36
+ </div>
37
+ );
38
+ }
@@ -0,0 +1,9 @@
1
+ import { Loader2 } from "lucide-solid";
2
+
3
+ export default function Loader() {
4
+ return (
5
+ <div class="flex h-full items-center justify-center pt-8">
6
+ <Loader2 class="animate-spin" />
7
+ </div>
8
+ );
9
+ }
@@ -0,0 +1,32 @@
1
+ import { RouterProvider, createRouter } from "@tanstack/solid-router";
2
+ import { render } from "solid-js/web";
3
+ import { routeTree } from "./routeTree.gen";
4
+ import "./styles.css";
5
+ import { QueryClientProvider } from "@tanstack/solid-query";
6
+ import { queryClient } from "./utils/orpc";
7
+
8
+ const router = createRouter({
9
+ routeTree,
10
+ defaultPreload: "intent",
11
+ scrollRestoration: true,
12
+ defaultPreloadStaleTime: 0,
13
+ });
14
+
15
+ declare module "@tanstack/solid-router" {
16
+ interface Register {
17
+ router: typeof router;
18
+ }
19
+ }
20
+
21
+ function App() {
22
+ return (
23
+ <QueryClientProvider client={queryClient}>
24
+ <RouterProvider router={router} />
25
+ </QueryClientProvider>
26
+ );
27
+ }
28
+
29
+ const rootElement = document.getElementById("app");
30
+ if (rootElement) {
31
+ render(() => <App />, rootElement);
32
+ }
@@ -0,0 +1,21 @@
1
+ import Header from "@/components/header";
2
+ import { Outlet, createRootRouteWithContext } from "@tanstack/solid-router";
3
+ import { TanStackRouterDevtools } from "@tanstack/solid-router-devtools";
4
+ import { SolidQueryDevtools } from "@tanstack/solid-query-devtools";
5
+
6
+ export const Route = createRootRouteWithContext()({
7
+ component: RootComponent,
8
+ });
9
+
10
+ function RootComponent() {
11
+ return (
12
+ <>
13
+ <div class="grid grid-rows-[auto_1fr] h-svh">
14
+ <Header />
15
+ <Outlet />
16
+ </div>
17
+ <SolidQueryDevtools />
18
+ <TanStackRouterDevtools />
19
+ </>
20
+ );
21
+ }