create-better-t-stack 2.2.3 → 2.3.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 +74 -128
- package/package.json +4 -4
- 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/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/dist/index.d.ts +0 -2
- /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
|
@@ -8,17 +8,24 @@ import {
|
|
|
8
8
|
} from "@/components/ui/card";
|
|
9
9
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
10
10
|
import { Input } from "@/components/ui/input";
|
|
11
|
-
{{#if (eq api "orpc")}}
|
|
12
|
-
import { orpc } from "@/utils/orpc";
|
|
13
|
-
{{/if}}
|
|
14
|
-
{{#if (eq api "trpc")}}
|
|
15
|
-
import { trpc } from "@/utils/trpc";
|
|
16
|
-
{{/if}}
|
|
17
|
-
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
18
11
|
import { createFileRoute } from "@tanstack/react-router";
|
|
19
12
|
import { Loader2, Trash2 } from "lucide-react";
|
|
20
13
|
import { useState } from "react";
|
|
21
14
|
|
|
15
|
+
{{#if (eq backend "convex")}}
|
|
16
|
+
import { useMutation, useQuery } from "convex/react";
|
|
17
|
+
import { api } from "@{{projectName}}/backend/convex/_generated/api.js";
|
|
18
|
+
import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel.d.ts";
|
|
19
|
+
{{else}}
|
|
20
|
+
{{#if (eq api "orpc")}}
|
|
21
|
+
import { orpc } from "@/utils/orpc";
|
|
22
|
+
{{/if}}
|
|
23
|
+
{{#if (eq api "trpc")}}
|
|
24
|
+
import { trpc } from "@/utils/trpc";
|
|
25
|
+
{{/if}}
|
|
26
|
+
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
27
|
+
{{/if}}
|
|
28
|
+
|
|
22
29
|
export const Route = createFileRoute("/todos")({
|
|
23
30
|
component: TodosRoute,
|
|
24
31
|
});
|
|
@@ -26,48 +33,70 @@ export const Route = createFileRoute("/todos")({
|
|
|
26
33
|
function TodosRoute() {
|
|
27
34
|
const [newTodoText, setNewTodoText] = useState("");
|
|
28
35
|
|
|
29
|
-
{{#if (eq
|
|
30
|
-
const todos = useQuery(
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
{{
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
36
|
+
{{#if (eq backend "convex")}}
|
|
37
|
+
const todos = useQuery(api.todos.getAll);
|
|
38
|
+
const createTodo = useMutation(api.todos.create);
|
|
39
|
+
const toggleTodo = useMutation(api.todos.toggle);
|
|
40
|
+
const deleteTodo = useMutation(api.todos.deleteTodo);
|
|
41
|
+
|
|
42
|
+
const handleAddTodo = async (e: React.FormEvent) => {
|
|
43
|
+
e.preventDefault();
|
|
44
|
+
const text = newTodoText.trim();
|
|
45
|
+
if (!text) return;
|
|
46
|
+
await createTodo({ text });
|
|
47
|
+
setNewTodoText("");
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const handleToggleTodo = (id: Id<"todos">, currentCompleted: boolean) => {
|
|
51
|
+
toggleTodo({ id, completed: !currentCompleted });
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleDeleteTodo = (id: Id<"todos">) => {
|
|
55
|
+
deleteTodo({ id });
|
|
56
|
+
};
|
|
57
|
+
{{else}}
|
|
58
|
+
{{#if (eq api "orpc")}}
|
|
59
|
+
const todos = useQuery(orpc.todo.getAll.queryOptions());
|
|
60
|
+
const createMutation = useMutation(
|
|
61
|
+
orpc.todo.create.mutationOptions({
|
|
62
|
+
onSuccess: () => {
|
|
63
|
+
todos.refetch();
|
|
64
|
+
setNewTodoText("");
|
|
65
|
+
},
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
const toggleMutation = useMutation(
|
|
69
|
+
orpc.todo.toggle.mutationOptions({
|
|
70
|
+
onSuccess: () => todos.refetch(),
|
|
71
|
+
}),
|
|
72
|
+
);
|
|
73
|
+
const deleteMutation = useMutation(
|
|
74
|
+
orpc.todo.delete.mutationOptions({
|
|
75
|
+
onSuccess: () => todos.refetch(),
|
|
76
|
+
}),
|
|
77
|
+
);
|
|
78
|
+
{{/if}}
|
|
79
|
+
{{#if (eq api "trpc")}}
|
|
80
|
+
const todos = useQuery(trpc.todo.getAll.queryOptions());
|
|
81
|
+
const createMutation = useMutation(
|
|
82
|
+
trpc.todo.create.mutationOptions({
|
|
83
|
+
onSuccess: () => {
|
|
84
|
+
todos.refetch();
|
|
85
|
+
setNewTodoText("");
|
|
86
|
+
},
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
const toggleMutation = useMutation(
|
|
90
|
+
trpc.todo.toggle.mutationOptions({
|
|
91
|
+
onSuccess: () => todos.refetch(),
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
94
|
+
const deleteMutation = useMutation(
|
|
95
|
+
trpc.todo.delete.mutationOptions({
|
|
96
|
+
onSuccess: () => todos.refetch(),
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
99
|
+
{{/if}}
|
|
71
100
|
|
|
72
101
|
const handleAddTodo = (e: React.FormEvent) => {
|
|
73
102
|
e.preventDefault();
|
|
@@ -83,6 +112,7 @@ function TodosRoute() {
|
|
|
83
112
|
const handleDeleteTodo = (id: number) => {
|
|
84
113
|
deleteMutation.mutate({ id });
|
|
85
114
|
};
|
|
115
|
+
{{/if}}
|
|
86
116
|
|
|
87
117
|
return (
|
|
88
118
|
<div className="mx-auto w-full max-w-md py-10">
|
|
@@ -100,60 +130,116 @@ function TodosRoute() {
|
|
|
100
130
|
value={newTodoText}
|
|
101
131
|
onChange={(e) => setNewTodoText(e.target.value)}
|
|
102
132
|
placeholder="Add a new task..."
|
|
133
|
+
{{#if (eq backend "convex")}}
|
|
134
|
+
{{else}}
|
|
103
135
|
disabled={createMutation.isPending}
|
|
136
|
+
{{/if}}
|
|
104
137
|
/>
|
|
105
138
|
<Button
|
|
106
139
|
type="submit"
|
|
140
|
+
{{#if (eq backend "convex")}}
|
|
141
|
+
disabled={!newTodoText.trim()}
|
|
142
|
+
{{else}}
|
|
107
143
|
disabled={createMutation.isPending || !newTodoText.trim()}
|
|
144
|
+
{{/if}}
|
|
108
145
|
>
|
|
109
|
-
{
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
146
|
+
{{#if (eq backend "convex")}}
|
|
147
|
+
Add
|
|
148
|
+
{{else}}
|
|
149
|
+
{createMutation.isPending ? (
|
|
150
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
151
|
+
) : (
|
|
152
|
+
"Add"
|
|
153
|
+
)}
|
|
154
|
+
{{/if}}
|
|
114
155
|
</Button>
|
|
115
156
|
</form>
|
|
116
157
|
|
|
117
|
-
{
|
|
118
|
-
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
158
|
+
{{#if (eq backend "convex")}}
|
|
159
|
+
{todos === undefined ? (
|
|
160
|
+
<div className="flex justify-center py-4">
|
|
161
|
+
<Loader2 className="h-6 w-6 animate-spin" />
|
|
162
|
+
</div>
|
|
163
|
+
) : todos.length === 0 ? (
|
|
164
|
+
<p className="py-4 text-center">No todos yet. Add one above!</p>
|
|
165
|
+
) : (
|
|
166
|
+
<ul className="space-y-2">
|
|
167
|
+
{todos.map((todo) => (
|
|
168
|
+
<li
|
|
169
|
+
key={todo._id}
|
|
170
|
+
className="flex items-center justify-between rounded-md border p-2"
|
|
171
|
+
>
|
|
172
|
+
<div className="flex items-center space-x-2">
|
|
173
|
+
<Checkbox
|
|
174
|
+
checked={todo.completed}
|
|
175
|
+
onCheckedChange={() =>
|
|
176
|
+
handleToggleTodo(todo._id, todo.completed)
|
|
177
|
+
}
|
|
178
|
+
id={`todo-${todo._id}`}
|
|
179
|
+
/>
|
|
180
|
+
<label
|
|
181
|
+
htmlFor={`todo-${todo._id}`}
|
|
182
|
+
className={`${todo.completed ? "line-through text-muted-foreground" : ""}`}
|
|
183
|
+
>
|
|
184
|
+
{todo.text}
|
|
185
|
+
</label>
|
|
186
|
+
</div>
|
|
187
|
+
<Button
|
|
188
|
+
variant="ghost"
|
|
189
|
+
size="icon"
|
|
190
|
+
onClick={() => handleDeleteTodo(todo._id)}
|
|
191
|
+
aria-label="Delete todo"
|
|
141
192
|
>
|
|
142
|
-
|
|
143
|
-
</
|
|
144
|
-
</
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
193
|
+
<Trash2 className="h-4 w-4" />
|
|
194
|
+
</Button>
|
|
195
|
+
</li>
|
|
196
|
+
))}
|
|
197
|
+
</ul>
|
|
198
|
+
)}
|
|
199
|
+
{{else}}
|
|
200
|
+
{todos.isLoading ? (
|
|
201
|
+
<div className="flex justify-center py-4">
|
|
202
|
+
<Loader2 className="h-6 w-6 animate-spin" />
|
|
203
|
+
</div>
|
|
204
|
+
) : todos.data?.length === 0 ? (
|
|
205
|
+
<p className="py-4 text-center">
|
|
206
|
+
No todos yet. Add one above!
|
|
207
|
+
</p>
|
|
208
|
+
) : (
|
|
209
|
+
<ul className="space-y-2">
|
|
210
|
+
{todos.data?.map((todo) => (
|
|
211
|
+
<li
|
|
212
|
+
key={todo.id}
|
|
213
|
+
className="flex items-center justify-between rounded-md border p-2"
|
|
150
214
|
>
|
|
151
|
-
<
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
215
|
+
<div className="flex items-center space-x-2">
|
|
216
|
+
<Checkbox
|
|
217
|
+
checked={todo.completed}
|
|
218
|
+
onCheckedChange={() =>
|
|
219
|
+
handleToggleTodo(todo.id, todo.completed)
|
|
220
|
+
}
|
|
221
|
+
id={`todo-${todo.id}`}
|
|
222
|
+
/>
|
|
223
|
+
<label
|
|
224
|
+
htmlFor={`todo-${todo.id}`}
|
|
225
|
+
className={`${todo.completed ? "line-through" : ""}`}
|
|
226
|
+
>
|
|
227
|
+
{todo.text}
|
|
228
|
+
</label>
|
|
229
|
+
</div>
|
|
230
|
+
<Button
|
|
231
|
+
variant="ghost"
|
|
232
|
+
size="icon"
|
|
233
|
+
onClick={() => handleDeleteTodo(todo.id)}
|
|
234
|
+
aria-label="Delete todo"
|
|
235
|
+
>
|
|
236
|
+
<Trash2 className="h-4 w-4" />
|
|
237
|
+
</Button>
|
|
238
|
+
</li>
|
|
239
|
+
))}
|
|
240
|
+
</ul>
|
|
241
|
+
)}
|
|
242
|
+
{{/if}}
|
|
157
243
|
</CardContent>
|
|
158
244
|
</Card>
|
|
159
245
|
</div>
|
|
@@ -8,32 +8,79 @@ import {
|
|
|
8
8
|
} from "@/components/ui/card";
|
|
9
9
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
10
10
|
import { Input } from "@/components/ui/input";
|
|
11
|
-
{{#if (eq api "trpc")}}
|
|
12
|
-
import { useTRPC } from "@/utils/trpc";
|
|
13
|
-
{{/if}}
|
|
14
|
-
{{#if (eq api "orpc")}}
|
|
15
|
-
import { useORPC } from "@/utils/orpc";
|
|
16
|
-
{{/if}}
|
|
17
|
-
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
18
11
|
import { createFileRoute } from "@tanstack/react-router";
|
|
19
12
|
import { Loader2, Trash2 } from "lucide-react";
|
|
20
13
|
import { useState } from "react";
|
|
21
14
|
|
|
15
|
+
{{#if (eq backend "convex")}}
|
|
16
|
+
import { useSuspenseQuery } from "@tanstack/react-query";
|
|
17
|
+
import { convexQuery } from "@convex-dev/react-query";
|
|
18
|
+
import { useMutation } from "convex/react";
|
|
19
|
+
import { api } from "@{{projectName}}/backend/convex/_generated/api.js";
|
|
20
|
+
import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel.js";
|
|
21
|
+
{{else}}
|
|
22
|
+
{{#if (eq api "trpc")}}
|
|
23
|
+
import { useTRPC } from "@/utils/trpc";
|
|
24
|
+
{{/if}}
|
|
25
|
+
{{#if (eq api "orpc")}}
|
|
26
|
+
import { useORPC } from "@/utils/orpc";
|
|
27
|
+
{{/if}}
|
|
28
|
+
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
29
|
+
{{/if}}
|
|
30
|
+
|
|
22
31
|
export const Route = createFileRoute("/todos")({
|
|
23
32
|
component: TodosRoute,
|
|
24
33
|
});
|
|
25
34
|
|
|
26
35
|
function TodosRoute() {
|
|
27
|
-
|
|
36
|
+
const [newTodoText, setNewTodoText] = useState("");
|
|
37
|
+
|
|
38
|
+
{{#if (eq backend "convex")}}
|
|
39
|
+
const todosQuery = useSuspenseQuery(convexQuery(api.todos.getAll, {}));
|
|
40
|
+
const todos = todosQuery.data;
|
|
41
|
+
|
|
42
|
+
const createTodo = useMutation(api.todos.create);
|
|
43
|
+
const toggleTodo = useMutation(api.todos.toggle);
|
|
44
|
+
const removeTodo = useMutation(api.todos.deleteTodo);
|
|
45
|
+
|
|
46
|
+
const handleAddTodo = async (e: React.FormEvent) => {
|
|
47
|
+
e.preventDefault();
|
|
48
|
+
const text = newTodoText.trim();
|
|
49
|
+
if (text) {
|
|
50
|
+
setNewTodoText("");
|
|
51
|
+
try {
|
|
52
|
+
await createTodo({ text });
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error("Failed to add todo:", error);
|
|
55
|
+
setNewTodoText(text);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const handleToggleTodo = async (id: Id<"todos">, completed: boolean) => {
|
|
61
|
+
try {
|
|
62
|
+
await toggleTodo({ id, completed: !completed });
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error("Failed to toggle todo:", error);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const handleDeleteTodo = async (id: Id<"todos">) => {
|
|
69
|
+
try {
|
|
70
|
+
await removeTodo({ id });
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error("Failed to delete todo:", error);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
{{else}}
|
|
76
|
+
{{#if (eq api "trpc")}}
|
|
28
77
|
const trpc = useTRPC();
|
|
29
|
-
|
|
30
|
-
|
|
78
|
+
{{/if}}
|
|
79
|
+
{{#if (eq api "orpc")}}
|
|
31
80
|
const orpc = useORPC();
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const [newTodoText, setNewTodoText] = useState("");
|
|
81
|
+
{{/if}}
|
|
35
82
|
|
|
36
|
-
|
|
83
|
+
{{#if (eq api "trpc")}}
|
|
37
84
|
const todos = useQuery(trpc.todo.getAll.queryOptions());
|
|
38
85
|
const createMutation = useMutation(
|
|
39
86
|
trpc.todo.create.mutationOptions({
|
|
@@ -53,8 +100,8 @@ function TodosRoute() {
|
|
|
53
100
|
onSuccess: () => todos.refetch(),
|
|
54
101
|
}),
|
|
55
102
|
);
|
|
56
|
-
|
|
57
|
-
|
|
103
|
+
{{/if}}
|
|
104
|
+
{{#if (eq api "orpc")}}
|
|
58
105
|
const todos = useQuery(orpc.todo.getAll.queryOptions());
|
|
59
106
|
const createMutation = useMutation(
|
|
60
107
|
orpc.todo.create.mutationOptions({
|
|
@@ -74,7 +121,7 @@ function TodosRoute() {
|
|
|
74
121
|
onSuccess: () => todos.refetch(),
|
|
75
122
|
}),
|
|
76
123
|
);
|
|
77
|
-
|
|
124
|
+
{{/if}}
|
|
78
125
|
|
|
79
126
|
const handleAddTodo = (e: React.FormEvent) => {
|
|
80
127
|
e.preventDefault();
|
|
@@ -90,12 +137,13 @@ function TodosRoute() {
|
|
|
90
137
|
const handleDeleteTodo = (id: number) => {
|
|
91
138
|
deleteMutation.mutate({ id });
|
|
92
139
|
};
|
|
140
|
+
{{/if}}
|
|
93
141
|
|
|
94
142
|
return (
|
|
95
143
|
<div className="mx-auto w-full max-w-md py-10">
|
|
96
144
|
<Card>
|
|
97
145
|
<CardHeader>
|
|
98
|
-
<CardTitle>Todo List</CardTitle>
|
|
146
|
+
<CardTitle>Todo List{{#if (eq backend "convex")}} (Convex){{/if}}</CardTitle>
|
|
99
147
|
<CardDescription>Manage your tasks efficiently</CardDescription>
|
|
100
148
|
</CardHeader>
|
|
101
149
|
<CardContent>
|
|
@@ -107,20 +155,72 @@ function TodosRoute() {
|
|
|
107
155
|
value={newTodoText}
|
|
108
156
|
onChange={(e) => setNewTodoText(e.target.value)}
|
|
109
157
|
placeholder="Add a new task..."
|
|
158
|
+
{{#unless (eq backend "convex")}}
|
|
110
159
|
disabled={createMutation.isPending}
|
|
160
|
+
{{/unless}}
|
|
111
161
|
/>
|
|
112
162
|
<Button
|
|
113
163
|
type="submit"
|
|
164
|
+
{{#unless (eq backend "convex")}}
|
|
114
165
|
disabled={createMutation.isPending || !newTodoText.trim()}
|
|
166
|
+
{{else}}
|
|
167
|
+
disabled={!newTodoText.trim()}
|
|
168
|
+
{{/unless}}
|
|
115
169
|
>
|
|
170
|
+
{{#unless (eq backend "convex")}}
|
|
116
171
|
{createMutation.isPending ? (
|
|
117
172
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
118
173
|
) : (
|
|
119
174
|
"Add"
|
|
120
175
|
)}
|
|
176
|
+
{{else}}
|
|
177
|
+
Add
|
|
178
|
+
{{/unless}}
|
|
121
179
|
</Button>
|
|
122
180
|
</form>
|
|
123
181
|
|
|
182
|
+
{{#if (eq backend "convex")}}
|
|
183
|
+
{todos?.length === 0 ? (
|
|
184
|
+
<p className="py-4 text-center">No todos yet. Add one above!</p>
|
|
185
|
+
) : (
|
|
186
|
+
<ul className="space-y-2">
|
|
187
|
+
{todos?.map((todo) => (
|
|
188
|
+
<li
|
|
189
|
+
key={todo._id}
|
|
190
|
+
className="flex items-center justify-between rounded-md border p-2"
|
|
191
|
+
>
|
|
192
|
+
<div className="flex items-center space-x-2">
|
|
193
|
+
<Checkbox
|
|
194
|
+
checked={todo.completed}
|
|
195
|
+
onCheckedChange={() =>
|
|
196
|
+
handleToggleTodo(todo._id, todo.completed)
|
|
197
|
+
}
|
|
198
|
+
id={`todo-${todo._id}`}
|
|
199
|
+
/>
|
|
200
|
+
<label
|
|
201
|
+
htmlFor={`todo-${todo._id}`}
|
|
202
|
+
className={`${
|
|
203
|
+
todo.completed
|
|
204
|
+
? "text-muted-foreground line-through"
|
|
205
|
+
: ""
|
|
206
|
+
}`}
|
|
207
|
+
>
|
|
208
|
+
{todo.text}
|
|
209
|
+
</label>
|
|
210
|
+
</div>
|
|
211
|
+
<Button
|
|
212
|
+
variant="ghost"
|
|
213
|
+
size="icon"
|
|
214
|
+
onClick={() => handleDeleteTodo(todo._id)}
|
|
215
|
+
aria-label="Delete todo"
|
|
216
|
+
>
|
|
217
|
+
<Trash2 className="h-4 w-4" />
|
|
218
|
+
</Button>
|
|
219
|
+
</li>
|
|
220
|
+
))}
|
|
221
|
+
</ul>
|
|
222
|
+
)}
|
|
223
|
+
{{else}}
|
|
124
224
|
{todos.isLoading ? (
|
|
125
225
|
<div className="flex justify-center py-4">
|
|
126
226
|
<Loader2 className="h-6 w-6 animate-spin" />
|
|
@@ -161,6 +261,7 @@ function TodosRoute() {
|
|
|
161
261
|
))}
|
|
162
262
|
</ul>
|
|
163
263
|
)}
|
|
264
|
+
{{/if}}
|
|
164
265
|
</CardContent>
|
|
165
266
|
</Card>
|
|
166
267
|
</div>
|
|
@@ -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",
|