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.
- package/dist/index.js +21 -29
- package/package.json +1 -1
- package/templates/addons/turborepo/{turbo.json → turbo.json.hbs} +7 -1
- package/templates/api/orpc/server/base/src/lib/context.ts.hbs +0 -2
- package/templates/auth/web/nuxt/app/components/SignInForm.vue +0 -1
- package/templates/auth/web/nuxt/app/components/SignUpForm.vue +0 -1
- package/templates/auth/web/nuxt/app/components/UserMenu.vue +0 -1
- package/templates/backend/convex/packages/backend/_gitignore +2 -0
- package/templates/backend/convex/packages/backend/convex/README.md +90 -0
- package/templates/backend/convex/packages/backend/convex/healthCheck.ts +7 -0
- package/templates/backend/convex/packages/backend/convex/schema.ts +9 -0
- package/templates/backend/convex/packages/backend/convex/todos.ts +42 -0
- package/templates/backend/convex/packages/backend/convex/tsconfig.json +25 -0
- package/templates/backend/convex/packages/backend/package.json.hbs +21 -0
- package/templates/base/package.json +2 -1
- package/templates/examples/todo/web/react/react-router/src/routes/todos.tsx.hbs +178 -93
- package/templates/examples/todo/web/react/tanstack-router/src/routes/todos.tsx.hbs +178 -92
- package/templates/examples/todo/web/react/tanstack-start/src/routes/todos.tsx.hbs +119 -18
- package/templates/examples/todo/web/svelte/src/routes/todos/+page.svelte.hbs +357 -0
- package/templates/extras/pnpm-workspace.yaml +1 -0
- package/templates/frontend/native/app/(drawer)/index.tsx.hbs +35 -7
- package/templates/frontend/native/app/_layout.tsx.hbs +27 -0
- package/templates/frontend/react/next/package.json +0 -2
- package/templates/frontend/react/next/src/app/page.tsx.hbs +26 -44
- package/templates/frontend/react/next/src/components/providers.tsx.hbs +15 -3
- package/templates/frontend/react/react-router/package.json +0 -2
- package/templates/frontend/react/react-router/src/root.tsx.hbs +31 -11
- package/templates/frontend/react/react-router/src/routes/_index.tsx.hbs +28 -47
- package/templates/frontend/react/tanstack-router/package.json +0 -2
- package/templates/frontend/react/tanstack-router/src/main.tsx.hbs +18 -2
- package/templates/frontend/react/tanstack-router/src/routes/__root.tsx.hbs +24 -1
- package/templates/frontend/react/tanstack-router/src/routes/index.tsx.hbs +24 -39
- package/templates/frontend/react/tanstack-start/src/router.tsx.hbs +57 -13
- package/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs +12 -10
- package/templates/frontend/react/tanstack-start/src/routes/index.tsx.hbs +31 -45
- package/templates/frontend/svelte/src/routes/+layout.svelte.hbs +24 -0
- package/templates/frontend/svelte/src/routes/+page.svelte.hbs +59 -1
- package/templates/examples/todo/web/svelte/src/routes/todos/+page.svelte +0 -150
- /package/templates/backend/{elysia → server/elysia}/src/index.ts.hbs +0 -0
- /package/templates/backend/{express → server/express}/src/index.ts.hbs +0 -0
- /package/templates/backend/{hono → server/hono}/src/index.ts.hbs +0 -0
- /package/templates/backend/{next → server/next}/next-env.d.ts +0 -0
- /package/templates/backend/{next → server/next}/next.config.ts +0 -0
- /package/templates/backend/{next → server/next}/package.json +0 -0
- /package/templates/backend/{next → server/next}/src/app/route.ts +0 -0
- /package/templates/backend/{next → server/next}/src/middleware.ts +0 -0
- /package/templates/backend/{next → server/next}/tsconfig.json +0 -0
- /package/templates/backend/{server-base → server/server-base}/_gitignore +0 -0
- /package/templates/backend/{server-base → server/server-base}/package.json +0 -0
- /package/templates/backend/{server-base → server/server-base}/src/routers/index.ts.hbs +0 -0
- /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,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
|
-
|
|
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
|
-
{
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
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
|
|
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
|
-
{{
|
|
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",
|