create-better-t-stack 2.2.4 → 2.4.0

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 (51) hide show
  1. package/dist/index.js +21 -29
  2. package/package.json +1 -1
  3. package/templates/addons/turborepo/{turbo.json → turbo.json.hbs} +7 -1
  4. package/templates/api/orpc/server/base/src/lib/context.ts.hbs +0 -2
  5. package/templates/auth/web/nuxt/app/components/SignInForm.vue +0 -1
  6. package/templates/auth/web/nuxt/app/components/SignUpForm.vue +0 -1
  7. package/templates/auth/web/nuxt/app/components/UserMenu.vue +0 -1
  8. package/templates/backend/convex/packages/backend/_gitignore +2 -0
  9. package/templates/backend/convex/packages/backend/convex/README.md +90 -0
  10. package/templates/backend/convex/packages/backend/convex/healthCheck.ts +7 -0
  11. package/templates/backend/convex/packages/backend/convex/schema.ts +9 -0
  12. package/templates/backend/convex/packages/backend/convex/todos.ts +42 -0
  13. package/templates/backend/convex/packages/backend/convex/tsconfig.json +25 -0
  14. package/templates/backend/convex/packages/backend/package.json.hbs +21 -0
  15. package/templates/base/package.json +2 -1
  16. package/templates/examples/todo/web/react/react-router/src/routes/todos.tsx.hbs +178 -93
  17. package/templates/examples/todo/web/react/tanstack-router/src/routes/todos.tsx.hbs +178 -92
  18. package/templates/examples/todo/web/react/tanstack-start/src/routes/todos.tsx.hbs +119 -18
  19. package/templates/examples/todo/web/svelte/src/routes/todos/+page.svelte.hbs +357 -0
  20. package/templates/extras/pnpm-workspace.yaml +1 -0
  21. package/templates/frontend/native/app/(drawer)/index.tsx.hbs +35 -7
  22. package/templates/frontend/native/app/_layout.tsx.hbs +27 -0
  23. package/templates/frontend/react/next/package.json +0 -2
  24. package/templates/frontend/react/next/src/app/page.tsx.hbs +26 -44
  25. package/templates/frontend/react/next/src/components/providers.tsx.hbs +15 -3
  26. package/templates/frontend/react/react-router/package.json +0 -2
  27. package/templates/frontend/react/react-router/src/root.tsx.hbs +31 -11
  28. package/templates/frontend/react/react-router/src/routes/_index.tsx.hbs +28 -47
  29. package/templates/frontend/react/tanstack-router/package.json +0 -2
  30. package/templates/frontend/react/tanstack-router/src/main.tsx.hbs +18 -2
  31. package/templates/frontend/react/tanstack-router/src/routes/__root.tsx.hbs +24 -1
  32. package/templates/frontend/react/tanstack-router/src/routes/index.tsx.hbs +24 -39
  33. package/templates/frontend/react/tanstack-start/src/router.tsx.hbs +57 -13
  34. package/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs +12 -10
  35. package/templates/frontend/react/tanstack-start/src/routes/index.tsx.hbs +31 -45
  36. package/templates/frontend/svelte/src/routes/+layout.svelte.hbs +24 -0
  37. package/templates/frontend/svelte/src/routes/+page.svelte.hbs +59 -1
  38. package/templates/examples/todo/web/svelte/src/routes/todos/+page.svelte +0 -150
  39. /package/templates/backend/{elysia → server/elysia}/src/index.ts.hbs +0 -0
  40. /package/templates/backend/{express → server/express}/src/index.ts.hbs +0 -0
  41. /package/templates/backend/{hono → server/hono}/src/index.ts.hbs +0 -0
  42. /package/templates/backend/{next → server/next}/next-env.d.ts +0 -0
  43. /package/templates/backend/{next → server/next}/next.config.ts +0 -0
  44. /package/templates/backend/{next → server/next}/package.json +0 -0
  45. /package/templates/backend/{next → server/next}/src/app/route.ts +0 -0
  46. /package/templates/backend/{next → server/next}/src/middleware.ts +0 -0
  47. /package/templates/backend/{next → server/next}/tsconfig.json +0 -0
  48. /package/templates/backend/{server-base → server/server-base}/_gitignore +0 -0
  49. /package/templates/backend/{server-base → server/server-base}/package.json +0 -0
  50. /package/templates/backend/{server-base → server/server-base}/src/routers/index.ts.hbs +0 -0
  51. /package/templates/backend/{server-base → server/server-base}/tsconfig.json.hbs +0 -0
@@ -0,0 +1,357 @@
1
+ {{#if (eq backend "convex")}}
2
+ <script lang="ts">
3
+ import { useQuery, useConvexClient } from 'convex-svelte';
4
+ import { api } from '@{{projectName}}/backend/convex/_generated/api.js';
5
+ import type { Id } from '@{{projectName}}/backend/convex/_generated/dataModel.js';
6
+
7
+ let newTodoText = $state('');
8
+ let isAdding = $state(false);
9
+ let addError = $state<Error | null>(null);
10
+ let togglingId = $state<Id<'todos'> | null>(null);
11
+ let toggleError = $state<Error | null>(null);
12
+ let deletingId = $state<Id<'todos'> | null>(null);
13
+ let deleteError = $state<Error | null>(null);
14
+
15
+ const client = useConvexClient();
16
+
17
+ const todosQuery = useQuery(api.todos.getAll, {});
18
+
19
+ async function handleAddTodo(event: SubmitEvent) {
20
+ event.preventDefault();
21
+ const text = newTodoText.trim();
22
+ if (!text || isAdding) return;
23
+
24
+ isAdding = true;
25
+ addError = null;
26
+ try {
27
+ await client.mutation(api.todos.create, { text });
28
+ newTodoText = '';
29
+ } catch (err) {
30
+ console.error('Failed to add todo:', err);
31
+ addError = err instanceof Error ? err : new Error(String(err));
32
+ } finally {
33
+ isAdding = false;
34
+ }
35
+ }
36
+
37
+ async function handleToggleTodo(id: Id<'todos'>, completed: boolean) {
38
+ if (togglingId === id || deletingId === id) return;
39
+
40
+ togglingId = id;
41
+ toggleError = null;
42
+ try {
43
+ await client.mutation(api.todos.toggle, { id, completed: !completed });
44
+ } catch (err) {
45
+ console.error('Failed to toggle todo:', err);
46
+ toggleError = err instanceof Error ? err : new Error(String(err));
47
+ } finally {
48
+ if (togglingId === id) {
49
+ togglingId = null;
50
+ }
51
+ }
52
+ }
53
+
54
+ async function handleDeleteTodo(id: Id<'todos'>) {
55
+ if (togglingId === id || deletingId === id) return;
56
+
57
+ deletingId = id;
58
+ deleteError = null;
59
+ try {
60
+ await client.mutation(api.todos.deleteTodo, { id });
61
+ } catch (err) {
62
+ console.error('Failed to delete todo:', err);
63
+ deleteError = err instanceof Error ? err : new Error(String(err));
64
+ } finally {
65
+ if (deletingId === id) {
66
+ deletingId = null;
67
+ }
68
+ }
69
+ }
70
+
71
+ const canAdd = $derived(!isAdding && newTodoText.trim().length > 0);
72
+ const isLoadingTodos = $derived(todosQuery.isLoading);
73
+ const todos = $derived(todosQuery.data ?? []);
74
+ const hasTodos = $derived(todos.length > 0);
75
+
76
+ </script>
77
+
78
+ <div class="p-4">
79
+ <h1 class="text-xl mb-4">Todos (Convex)</h1>
80
+
81
+ <form onsubmit={handleAddTodo} class="flex gap-2 mb-4">
82
+ <input
83
+ type="text"
84
+ bind:value={newTodoText}
85
+ placeholder="New task..."
86
+ disabled={isAdding}
87
+ class="p-1 flex-grow"
88
+ />
89
+ <button
90
+ type="submit"
91
+ disabled={!canAdd}
92
+ class="bg-blue-500 text-white px-3 py-1 rounded disabled:opacity-50"
93
+ >
94
+ {#if isAdding}Adding...{:else}Add{/if}
95
+ </button>
96
+ </form>
97
+
98
+ {#if isLoadingTodos}
99
+ <p>Loading...</p>
100
+ {:else if !hasTodos}
101
+ <p>No todos yet.</p>
102
+ {:else}
103
+ <ul class="space-y-1">
104
+ {#each todos as todo (todo._id)}
105
+ {@const isTogglingThis = togglingId === todo._id}
106
+ {@const isDeletingThis = deletingId === todo._id}
107
+ {@const isDisabled = isTogglingThis || isDeletingThis}
108
+ <li
109
+ class="flex items-center justify-between p-2"
110
+ class:opacity-50={isDisabled}
111
+ >
112
+ <div class="flex items-center gap-2">
113
+ <input
114
+ type="checkbox"
115
+ id={`todo-${todo._id}`}
116
+ checked={todo.completed}
117
+ onchange={() => handleToggleTodo(todo._id, todo.completed)}
118
+ disabled={isDisabled}
119
+ />
120
+ <label
121
+ for={`todo-${todo._id}`}
122
+ class:line-through={todo.completed}
123
+ >
124
+ {todo.text}
125
+ </label>
126
+ </div>
127
+ <button
128
+ type="button"
129
+ onclick={() => handleDeleteTodo(todo._id)}
130
+ disabled={isDisabled}
131
+ aria-label="Delete todo"
132
+ class="text-red-500 px-1 disabled:opacity-50"
133
+ >
134
+ {#if isDeletingThis}Deleting...{:else}X{/if}
135
+ </button>
136
+ </li>
137
+ {/each}
138
+ </ul>
139
+ {/if}
140
+
141
+ {#if todosQuery.error}
142
+ <p class="mt-4 text-red-500">
143
+ Error loading: {todosQuery.error?.message ?? 'Unknown error'}
144
+ </p>
145
+ {/if}
146
+ {#if addError}
147
+ <p class="mt-4 text-red-500">
148
+ Error adding: {addError.message ?? 'Unknown error'}
149
+ </p>
150
+ {/if}
151
+ {#if toggleError}
152
+ <p class="mt-4 text-red-500">
153
+ Error updating: {toggleError.message ?? 'Unknown error'}
154
+ </p>
155
+ {/if}
156
+ {#if deleteError}
157
+ <p class="mt-4 text-red-500">
158
+ Error deleting: {deleteError.message ?? 'Unknown error'}
159
+ </p>
160
+ {/if}
161
+ </div>
162
+ {{else}}
163
+ <script lang="ts">
164
+ {{#if (eq api "orpc")}}
165
+ import { orpc } from '$lib/orpc';
166
+ {{/if}}
167
+ {{#if (eq api "trpc")}}
168
+ import { trpc } from '$lib/trpc';
169
+ {{/if}}
170
+ import { createQuery, createMutation } from '@tanstack/svelte-query';
171
+
172
+ let newTodoText = $state('');
173
+
174
+ {{#if (eq api "orpc")}}
175
+ const todosQuery = createQuery(orpc.todo.getAll.queryOptions());
176
+
177
+ const addMutation = createMutation(
178
+ orpc.todo.create.mutationOptions({
179
+ onSuccess: () => {
180
+ $todosQuery.refetch();
181
+ newTodoText = '';
182
+ },
183
+ onError: (error) => {
184
+ console.error('Failed to create todo:', error?.message ?? error);
185
+ },
186
+ })
187
+ );
188
+
189
+ const toggleMutation = createMutation(
190
+ orpc.todo.toggle.mutationOptions({
191
+ onSuccess: () => {
192
+ $todosQuery.refetch();
193
+ },
194
+ onError: (error) => {
195
+ console.error('Failed to toggle todo:', error?.message ?? error);
196
+ },
197
+ })
198
+ );
199
+
200
+ const deleteMutation = createMutation(
201
+ orpc.todo.delete.mutationOptions({
202
+ onSuccess: () => {
203
+ $todosQuery.refetch();
204
+ },
205
+ onError: (error) => {
206
+ console.error('Failed to delete todo:', error?.message ?? error);
207
+ },
208
+ })
209
+ );
210
+ {{/if}}
211
+ {{#if (eq api "trpc")}}
212
+ const todosQuery = createQuery(trpc.todo.getAll.queryOptions());
213
+
214
+ const addMutation = createMutation(
215
+ trpc.todo.create.mutationOptions({
216
+ onSuccess: () => {
217
+ $todosQuery.refetch();
218
+ newTodoText = '';
219
+ },
220
+ onError: (error) => {
221
+ console.error('Failed to create todo:', error?.message ?? error);
222
+ },
223
+ })
224
+ );
225
+
226
+ const toggleMutation = createMutation(
227
+ trpc.todo.toggle.mutationOptions({
228
+ onSuccess: () => {
229
+ $todosQuery.refetch();
230
+ },
231
+ onError: (error) => {
232
+ console.error('Failed to toggle todo:', error?.message ?? error);
233
+ },
234
+ })
235
+ );
236
+
237
+ const deleteMutation = createMutation(
238
+ trpc.todo.delete.mutationOptions({
239
+ onSuccess: () => {
240
+ $todosQuery.refetch();
241
+ },
242
+ onError: (error) => {
243
+ console.error('Failed to delete todo:', error?.message ?? error);
244
+ },
245
+ })
246
+ );
247
+ {{/if}}
248
+
249
+ function handleAddTodo(event: SubmitEvent) {
250
+ event.preventDefault();
251
+ const text = newTodoText.trim();
252
+ if (text) {
253
+ $addMutation.mutate({ text });
254
+ }
255
+ }
256
+
257
+ function handleToggleTodo(id: number, completed: boolean) {
258
+ $toggleMutation.mutate({ id, completed: !completed });
259
+ }
260
+
261
+ function handleDeleteTodo(id: number) {
262
+ $deleteMutation.mutate({ id });
263
+ }
264
+
265
+ const isAdding = $derived($addMutation.isPending);
266
+ const canAdd = $derived(!isAdding && newTodoText.trim().length > 0);
267
+ const isLoadingTodos = $derived($todosQuery.isLoading);
268
+ const todos = $derived($todosQuery.data ?? []);
269
+ const hasTodos = $derived(todos.length > 0);
270
+
271
+ </script>
272
+
273
+ <div class="p-4">
274
+ <h1 class="text-xl mb-4">Todos{{#if (eq api "trpc")}} (tRPC){{/if}}{{#if (eq api "orpc")}} (oRPC){{/if}}</h1>
275
+
276
+ <form onsubmit={handleAddTodo} class="flex gap-2 mb-4">
277
+ <input
278
+ type="text"
279
+ bind:value={newTodoText}
280
+ placeholder="New task..."
281
+ disabled={isAdding}
282
+ class=" p-1 flex-grow"
283
+ />
284
+ <button
285
+ type="submit"
286
+ disabled={!canAdd}
287
+ class="bg-blue-500 text-white px-3 py-1 rounded disabled:opacity-50"
288
+ >
289
+ {#if isAdding}Adding...{:else}Add{/if}
290
+ </button>
291
+ </form>
292
+
293
+ {#if isLoadingTodos}
294
+ <p>Loading...</p>
295
+ {:else if !hasTodos}
296
+ <p>No todos yet.</p>
297
+ {:else}
298
+ <ul class="space-y-1">
299
+ {#each todos as todo (todo.id)}
300
+ {@const isToggling = $toggleMutation.isPending && $toggleMutation.variables?.id === todo.id}
301
+ {@const isDeleting = $deleteMutation.isPending && $deleteMutation.variables?.id === todo.id}
302
+ {@const isDisabled = isToggling || isDeleting}
303
+ <li
304
+ class="flex items-center justify-between p-2 "
305
+ class:opacity-50={isDisabled}
306
+ >
307
+ <div class="flex items-center gap-2">
308
+ <input
309
+ type="checkbox"
310
+ id={`todo-${todo.id}`}
311
+ checked={todo.completed}
312
+ onchange={() => handleToggleTodo(todo.id, todo.completed)}
313
+ disabled={isDisabled}
314
+ />
315
+ <label
316
+ for={`todo-${todo.id}`}
317
+ class:line-through={todo.completed}
318
+ >
319
+ {todo.text}
320
+ </label>
321
+ </div>
322
+ <button
323
+ type="button"
324
+ onclick={() => handleDeleteTodo(todo.id)}
325
+ disabled={isDisabled}
326
+ aria-label="Delete todo"
327
+ class="text-red-500 px-1 disabled:opacity-50"
328
+ >
329
+ {#if isDeleting}Deleting...{:else}X{/if}
330
+ </button>
331
+ </li>
332
+ {/each}
333
+ </ul>
334
+ {/if}
335
+
336
+ {#if $todosQuery.isError}
337
+ <p class="mt-4 text-red-500">
338
+ Error loading: {$todosQuery.error?.message ?? 'Unknown error'}
339
+ </p>
340
+ {/if}
341
+ {#if $addMutation.isError}
342
+ <p class="mt-4 text-red-500">
343
+ Error adding: {$addMutation.error?.message ?? 'Unknown error'}
344
+ </p>
345
+ {/if}
346
+ {#if $toggleMutation.isError}
347
+ <p class="mt-4 text-red-500">
348
+ Error updating: {$toggleMutation.error?.message ?? 'Unknown error'}
349
+ </p>
350
+ {/if}
351
+ {#if $deleteMutation.isError}
352
+ <p class="mt-4 text-red-500">
353
+ Error deleting: {$deleteMutation.error?.message ?? 'Unknown error'}
354
+ </p>
355
+ {/if}
356
+ </div>
357
+ {{/if}}
@@ -1,2 +1,3 @@
1
1
  packages:
2
2
  - "apps/*"
3
+ - "packages/*"
@@ -1,12 +1,17 @@
1
- import { useQuery } from "@tanstack/react-query";
2
1
  import { View, Text, ScrollView } from "react-native";
3
2
  import { Container } from "@/components/container";
4
3
  {{#if (eq api "orpc")}}
4
+ import { useQuery } from "@tanstack/react-query";
5
5
  import { orpc } from "@/utils/orpc";
6
6
  {{/if}}
7
7
  {{#if (eq api "trpc")}}
8
+ import { useQuery } from "@tanstack/react-query";
8
9
  import { trpc } from "@/utils/trpc";
9
10
  {{/if}}
11
+ {{#if (eq backend "convex")}}
12
+ import { useQuery } from "convex/react";
13
+ import { api } from "@{{ projectName }}/backend/convex/_generated/api.js";
14
+ {{/if}}
10
15
 
11
16
  export default function Home() {
12
17
  {{#if (eq api "orpc")}}
@@ -15,6 +20,9 @@ export default function Home() {
15
20
  {{#if (eq api "trpc")}}
16
21
  const healthCheck = useQuery(trpc.healthCheck.queryOptions());
17
22
  {{/if}}
23
+ {{#if (eq backend "convex")}}
24
+ const healthCheck = useQuery(api.healthCheck.get);
25
+ {{/if}}
18
26
 
19
27
  return (
20
28
  <Container>
@@ -28,15 +36,35 @@ export default function Home() {
28
36
  <View className="flex-row items-center gap-2">
29
37
  <View
30
38
  className={`h-2.5 w-2.5 rounded-full ${
31
- healthCheck.data ? "bg-green-500" : "bg-red-500"
39
+ {{#if (or (eq api "orpc") (eq api "trpc"))}}
40
+ healthCheck.data ? "bg-green-500" : "bg-red-500"
41
+ {{else}}
42
+ healthCheck ? "bg-green-500" : "bg-red-500"
43
+ {{/if}}
32
44
  }`}
33
45
  />
34
46
  <Text className="text-sm text-foreground">
35
- {healthCheck.isLoading
36
- ? "Checking..."
37
- : healthCheck.data
38
- ? "Connected"
39
- : "Disconnected"}
47
+ {{#if (eq api "orpc")}}
48
+ {healthCheck.isLoading
49
+ ? "Checking..."
50
+ : healthCheck.data
51
+ ? "Connected"
52
+ : "Disconnected"}
53
+ {{/if}}
54
+ {{#if (eq api "trpc")}}
55
+ {healthCheck.isLoading
56
+ ? "Checking..."
57
+ : healthCheck.data
58
+ ? "Connected"
59
+ : "Disconnected"}
60
+ {{/if}}
61
+ {{#if (eq backend "convex")}}
62
+ {healthCheck === undefined
63
+ ? "Checking..."
64
+ : healthCheck === "OK"
65
+ ? "Connected"
66
+ : "Error"}
67
+ {{/if}}
40
68
  </Text>
41
69
  </View>
42
70
  </View>
@@ -1,4 +1,8 @@
1
+ {{#if (eq backend "convex")}}
2
+ import { ConvexProvider, ConvexReactClient } from "convex/react";
3
+ {{else}}
1
4
  import { QueryClientProvider } from "@tanstack/react-query";
5
+ {{/if}}
2
6
  import { Stack } from "expo-router";
3
7
  import {
4
8
  DarkTheme,
@@ -35,6 +39,12 @@ export const unstable_settings = {
35
39
  initialRouteName: "(drawer)",
36
40
  };
37
41
 
42
+ {{#if (eq backend "convex")}}
43
+ const convex = new ConvexReactClient(process.env.EXPO_PUBLIC_CONVEX_URL!, {
44
+ unsavedChangesWarning: false,
45
+ });
46
+ {{/if}}
47
+
38
48
  export default function RootLayout() {
39
49
  const hasMounted = useRef(false);
40
50
  const { colorScheme, isDarkColorScheme } = useColorScheme();
@@ -58,6 +68,22 @@ export default function RootLayout() {
58
68
  return null;
59
69
  }
60
70
  return (
71
+ {{#if (eq backend "convex")}}
72
+ <ConvexProvider client={convex}>
73
+ <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>
74
+ <StatusBar style={isDarkColorScheme ? "light" : "dark"} />
75
+ <GestureHandlerRootView style=\{{ flex: 1 }}>
76
+ <Stack>
77
+ <Stack.Screen name="(drawer)" options=\{{ headerShown: false }} />
78
+ <Stack.Screen
79
+ name="modal"
80
+ options=\{{ title: "Modal", presentation: "modal" }}
81
+ />
82
+ </Stack>
83
+ </GestureHandlerRootView>
84
+ </ThemeProvider>
85
+ </ConvexProvider>
86
+ {{else}}
61
87
  <QueryClientProvider client={queryClient}>
62
88
  <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>
63
89
  <StatusBar style={isDarkColorScheme ? "light" : "dark"} />
@@ -72,6 +98,7 @@ export default function RootLayout() {
72
98
  </GestureHandlerRootView>
73
99
  </ThemeProvider>
74
100
  </QueryClientProvider>
101
+ {{/if}}
75
102
  );
76
103
  }
77
104
 
@@ -14,7 +14,6 @@
14
14
  "@radix-ui/react-label": "^2.1.3",
15
15
  "@radix-ui/react-slot": "^1.2.0",
16
16
  "@tanstack/react-form": "^1.3.2",
17
- "@tanstack/react-query": "^5.72.2",
18
17
  "class-variance-authority": "^0.7.1",
19
18
  "clsx": "^2.1.1",
20
19
  "lucide-react": "^0.487.0",
@@ -29,7 +28,6 @@
29
28
  },
30
29
  "devDependencies": {
31
30
  "@tailwindcss/postcss": "^4",
32
- "@tanstack/react-query-devtools": "^5.72.2",
33
31
  "@types/node": "^20",
34
32
  "@types/react": "^19",
35
33
  "@types/react-dom": "^19",
@@ -1,11 +1,16 @@
1
1
  "use client"
2
- {{#if (eq api "orpc")}}
2
+ {{#if (eq backend "convex")}}
3
+ import { useQuery } from "convex/react";
4
+ import { api } from "@{{projectName}}/backend/convex/_generated/api.js";
5
+ {{else}}
6
+ {{#if (eq api "orpc")}}
3
7
  import { orpc } from "@/utils/orpc";
4
- {{/if}}
5
- {{#if (eq api "trpc")}}
8
+ {{/if}}
9
+ {{#if (eq api "trpc")}}
6
10
  import { trpc } from "@/utils/trpc";
7
- {{/if}}
11
+ {{/if}}
8
12
  import { useQuery } from "@tanstack/react-query";
13
+ {{/if}}
9
14
 
10
15
  const TITLE_TEXT = `
11
16
  ██████╗ ███████╗████████╗████████╗███████╗██████╗
@@ -24,10 +29,11 @@ const TITLE_TEXT = `
24
29
  `;
25
30
 
26
31
  export default function Home() {
27
- {{#if (eq api "orpc")}}
32
+ {{#if (eq backend "convex")}}
33
+ const healthCheck = useQuery(api.healthCheck.get);
34
+ {{else if (eq api "orpc")}}
28
35
  const healthCheck = useQuery(orpc.healthCheck.queryOptions());
29
- {{/if}}
30
- {{#if (eq api "trpc")}}
36
+ {{else if (eq api "trpc")}}
31
37
  const healthCheck = useQuery(trpc.healthCheck.queryOptions());
32
38
  {{/if}}
33
39
 
@@ -38,6 +44,18 @@ export default function Home() {
38
44
  <section className="rounded-lg border p-4">
39
45
  <h2 className="mb-2 font-medium">API Status</h2>
40
46
  <div className="flex items-center gap-2">
47
+ {{#if (eq backend "convex")}}
48
+ <div
49
+ className={`h-2 w-2 rounded-full ${healthCheck === "OK" ? "bg-green-500" : healthCheck === undefined ? "bg-orange-400" : "bg-red-500"}`}
50
+ />
51
+ <span className="text-sm text-muted-foreground">
52
+ {healthCheck === undefined
53
+ ? "Checking..."
54
+ : healthCheck === "OK"
55
+ ? "Connected"
56
+ : "Error"}
57
+ </span>
58
+ {{else}}
41
59
  <div
42
60
  className={`h-2 w-2 rounded-full ${healthCheck.data ? "bg-green-500" : "bg-red-500"}`}
43
61
  />
@@ -48,46 +66,10 @@ export default function Home() {
48
66
  ? "Connected"
49
67
  : "Disconnected"}
50
68
  </span>
69
+ {{/if}}
51
70
  </div>
52
71
  </section>
53
-
54
- <section>
55
- <h2 className="mb-3 font-medium">Core Features</h2>
56
- <ul className="grid grid-cols-2 gap-3">
57
- <FeatureItem
58
- title="Type-Safe API"
59
- description="End-to-end type safety with tRPC"
60
- />
61
- <FeatureItem
62
- title="Modern React"
63
- description="TanStack Router + TanStack Query"
64
- />
65
- <FeatureItem
66
- title="Fast Backend"
67
- description="Lightweight Hono server"
68
- />
69
- <FeatureItem
70
- title="Beautiful UI"
71
- description="TailwindCSS + shadcn/ui components"
72
- />
73
- </ul>
74
- </section>
75
72
  </div>
76
73
  </div>
77
74
  );
78
75
  }
79
-
80
- function FeatureItem({
81
- title,
82
- description,
83
- }: {
84
- title: string;
85
- description: string;
86
- }) {
87
- return (
88
- <li className="border-l-2 border-primary py-1 pl-3">
89
- <h3 className="font-medium">{title}</h3>
90
- <p className="text-sm text-muted-foreground">{description}</p>
91
- </li>
92
- );
93
- }
@@ -1,14 +1,22 @@
1
1
  "use client"
2
+ {{#if (eq backend "convex")}}
3
+ import { ConvexProvider, ConvexReactClient } from "convex/react";
4
+ {{else}}
2
5
  import { QueryClientProvider } from "@tanstack/react-query";
3
- {{#if (eq api "orpc")}}
6
+ {{#if (eq api "orpc")}}
4
7
  import { orpc, ORPCContext, queryClient } from "@/utils/orpc";
5
- {{/if}}
6
- {{#if (eq api "trpc")}}
8
+ {{/if}}
9
+ {{#if (eq api "trpc")}}
7
10
  import { queryClient } from "@/utils/trpc";
11
+ {{/if}}
8
12
  {{/if}}
9
13
  import { ThemeProvider } from "./theme-provider";
10
14
  import { Toaster } from "./ui/sonner";
11
15
 
16
+ {{#if (eq backend "convex")}}
17
+ const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
18
+ {{/if}}
19
+
12
20
  export default function Providers({
13
21
  children,
14
22
  }: {
@@ -21,6 +29,9 @@ export default function Providers({
21
29
  enableSystem
22
30
  disableTransitionOnChange
23
31
  >
32
+ {{#if (eq backend "convex")}}
33
+ <ConvexProvider client={convex}>{children}</ConvexProvider>
34
+ {{else}}
24
35
  <QueryClientProvider client={queryClient}>
25
36
  {{#if (eq api "orpc")}}
26
37
  <ORPCContext.Provider value={orpc}>
@@ -31,6 +42,7 @@ export default function Providers({
31
42
  {children}
32
43
  {{/if}}
33
44
  </QueryClientProvider>
45
+ {{/if}}
34
46
  <Toaster richColors />
35
47
  </ThemeProvider>
36
48
  )
@@ -17,7 +17,6 @@
17
17
  "@react-router/node": "^7.4.1",
18
18
  "@react-router/serve": "^7.4.1",
19
19
  "@tanstack/react-form": "^1.2.3",
20
- "@tanstack/react-query": "^5.71.3",
21
20
  "class-variance-authority": "^0.7.1",
22
21
  "clsx": "^2.1.1",
23
22
  "isbot": "^5.1.17",
@@ -34,7 +33,6 @@
34
33
  "devDependencies": {
35
34
  "@react-router/dev": "^7.4.1",
36
35
  "@tailwindcss/vite": "^4.0.0",
37
- "@tanstack/react-query-devtools": "^5.71.3",
38
36
  "@types/node": "^20",
39
37
  "@types/react": "^19.0.1",
40
38
  "@types/react-dom": "^19.0.1",