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,42 @@
|
|
|
1
|
+
import { query, mutation } from "./_generated/server";
|
|
2
|
+
import { v } from "convex/values";
|
|
3
|
+
|
|
4
|
+
export const getAll = query({
|
|
5
|
+
handler: async (ctx) => {
|
|
6
|
+
return await ctx.db.query("todos").collect();
|
|
7
|
+
},
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const create = mutation({
|
|
11
|
+
args: {
|
|
12
|
+
text: v.string(),
|
|
13
|
+
},
|
|
14
|
+
handler: async (ctx, args) => {
|
|
15
|
+
const newTodoId = await ctx.db.insert("todos", {
|
|
16
|
+
text: args.text,
|
|
17
|
+
completed: false,
|
|
18
|
+
});
|
|
19
|
+
return await ctx.db.get(newTodoId);
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const toggle = mutation({
|
|
24
|
+
args: {
|
|
25
|
+
id: v.id("todos"),
|
|
26
|
+
completed: v.boolean(),
|
|
27
|
+
},
|
|
28
|
+
handler: async (ctx, args) => {
|
|
29
|
+
await ctx.db.patch(args.id, { completed: args.completed });
|
|
30
|
+
return { success: true };
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export const deleteTodo = mutation({
|
|
35
|
+
args: {
|
|
36
|
+
id: v.id("todos"),
|
|
37
|
+
},
|
|
38
|
+
handler: async (ctx, args) => {
|
|
39
|
+
await ctx.db.delete(args.id);
|
|
40
|
+
return { success: true };
|
|
41
|
+
},
|
|
42
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
/* This TypeScript project config describes the environment that
|
|
3
|
+
* Convex functions run in and is used to typecheck them.
|
|
4
|
+
* You can modify it, but some settings required to use Convex.
|
|
5
|
+
*/
|
|
6
|
+
"compilerOptions": {
|
|
7
|
+
/* These settings are not required by Convex and can be modified. */
|
|
8
|
+
"allowJs": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"moduleResolution": "Bundler",
|
|
11
|
+
"jsx": "react-jsx",
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"allowSyntheticDefaultImports": true,
|
|
14
|
+
|
|
15
|
+
/* These compiler options are required by Convex */
|
|
16
|
+
"target": "ESNext",
|
|
17
|
+
"lib": ["ES2021", "dom"],
|
|
18
|
+
"forceConsistentCasingInFileNames": true,
|
|
19
|
+
"module": "ESNext",
|
|
20
|
+
"isolatedModules": true,
|
|
21
|
+
"noEmit": true
|
|
22
|
+
},
|
|
23
|
+
"include": ["./**/*"],
|
|
24
|
+
"exclude": ["./_generated"]
|
|
25
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@{{projectName}}/backend",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"exports": {
|
|
6
|
+
"./convex/*": "./convex/*"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"dev": "convex dev",
|
|
10
|
+
"setup": "convex dev --until-success"
|
|
11
|
+
},
|
|
12
|
+
"author": "",
|
|
13
|
+
"license": "ISC",
|
|
14
|
+
"description": "",
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"typescript": "^5.8.3"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"convex": "^1.23.0"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -8,61 +8,90 @@ 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 { Loader2, Trash2 } from "lucide-react";
|
|
19
12
|
import { useState } from "react";
|
|
20
13
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
14
|
+
{{#if (eq backend "convex")}}
|
|
15
|
+
import { useMutation, useQuery } from "convex/react";
|
|
16
|
+
import { api } from "@{{projectName}}/backend/convex/_generated/api.js";
|
|
17
|
+
import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel.d.ts";
|
|
18
|
+
{{else}}
|
|
24
19
|
{{#if (eq api "orpc")}}
|
|
25
|
-
|
|
26
|
-
const createMutation = useMutation(
|
|
27
|
-
orpc.todo.create.mutationOptions({
|
|
28
|
-
onSuccess: () => {
|
|
29
|
-
todos.refetch();
|
|
30
|
-
setNewTodoText("");
|
|
31
|
-
},
|
|
32
|
-
})
|
|
33
|
-
);
|
|
34
|
-
const toggleMutation = useMutation(
|
|
35
|
-
orpc.todo.toggle.mutationOptions({
|
|
36
|
-
onSuccess: () => todos.refetch(),
|
|
37
|
-
})
|
|
38
|
-
);
|
|
39
|
-
const deleteMutation = useMutation(
|
|
40
|
-
orpc.todo.delete.mutationOptions({
|
|
41
|
-
onSuccess: () => todos.refetch(),
|
|
42
|
-
})
|
|
43
|
-
);
|
|
20
|
+
import { orpc } from "@/utils/orpc";
|
|
44
21
|
{{/if}}
|
|
45
22
|
{{#if (eq api "trpc")}}
|
|
46
|
-
|
|
47
|
-
const createMutation = useMutation(
|
|
48
|
-
trpc.todo.create.mutationOptions({
|
|
49
|
-
onSuccess: () => {
|
|
50
|
-
todos.refetch();
|
|
51
|
-
setNewTodoText("");
|
|
52
|
-
},
|
|
53
|
-
})
|
|
54
|
-
);
|
|
55
|
-
const toggleMutation = useMutation(
|
|
56
|
-
trpc.todo.toggle.mutationOptions({
|
|
57
|
-
onSuccess: () => todos.refetch(),
|
|
58
|
-
})
|
|
59
|
-
);
|
|
60
|
-
const deleteMutation = useMutation(
|
|
61
|
-
trpc.todo.delete.mutationOptions({
|
|
62
|
-
onSuccess: () => todos.refetch(),
|
|
63
|
-
})
|
|
64
|
-
);
|
|
23
|
+
import { trpc } from "@/utils/trpc";
|
|
65
24
|
{{/if}}
|
|
25
|
+
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
26
|
+
{{/if}}
|
|
27
|
+
|
|
28
|
+
export default function Todos() {
|
|
29
|
+
const [newTodoText, setNewTodoText] = useState("");
|
|
30
|
+
|
|
31
|
+
{{#if (eq backend "convex")}}
|
|
32
|
+
const todos = useQuery(api.todos.getAll);
|
|
33
|
+
const createTodo = useMutation(api.todos.create);
|
|
34
|
+
const toggleTodo = useMutation(api.todos.toggle);
|
|
35
|
+
const deleteTodo = useMutation(api.todos.deleteTodo);
|
|
36
|
+
|
|
37
|
+
const handleAddTodo = async (e: React.FormEvent) => {
|
|
38
|
+
e.preventDefault();
|
|
39
|
+
const text = newTodoText.trim();
|
|
40
|
+
if (!text) return;
|
|
41
|
+
await createTodo({ text });
|
|
42
|
+
setNewTodoText("");
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const handleToggleTodo = (id: Id<"todos">, currentCompleted: boolean) => {
|
|
46
|
+
toggleTodo({ id, completed: !currentCompleted });
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const handleDeleteTodo = (id: Id<"todos">) => {
|
|
50
|
+
deleteTodo({ id });
|
|
51
|
+
};
|
|
52
|
+
{{else}}
|
|
53
|
+
{{#if (eq api "orpc")}}
|
|
54
|
+
const todos = useQuery(orpc.todo.getAll.queryOptions());
|
|
55
|
+
const createMutation = useMutation(
|
|
56
|
+
orpc.todo.create.mutationOptions({
|
|
57
|
+
onSuccess: () => {
|
|
58
|
+
todos.refetch();
|
|
59
|
+
setNewTodoText("");
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
);
|
|
63
|
+
const toggleMutation = useMutation(
|
|
64
|
+
orpc.todo.toggle.mutationOptions({
|
|
65
|
+
onSuccess: () => todos.refetch(),
|
|
66
|
+
})
|
|
67
|
+
);
|
|
68
|
+
const deleteMutation = useMutation(
|
|
69
|
+
orpc.todo.delete.mutationOptions({
|
|
70
|
+
onSuccess: () => todos.refetch(),
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
{{/if}}
|
|
74
|
+
{{#if (eq api "trpc")}}
|
|
75
|
+
const todos = useQuery(trpc.todo.getAll.queryOptions());
|
|
76
|
+
const createMutation = useMutation(
|
|
77
|
+
trpc.todo.create.mutationOptions({
|
|
78
|
+
onSuccess: () => {
|
|
79
|
+
todos.refetch();
|
|
80
|
+
setNewTodoText("");
|
|
81
|
+
},
|
|
82
|
+
})
|
|
83
|
+
);
|
|
84
|
+
const toggleMutation = useMutation(
|
|
85
|
+
trpc.todo.toggle.mutationOptions({
|
|
86
|
+
onSuccess: () => todos.refetch(),
|
|
87
|
+
})
|
|
88
|
+
);
|
|
89
|
+
const deleteMutation = useMutation(
|
|
90
|
+
trpc.todo.delete.mutationOptions({
|
|
91
|
+
onSuccess: () => todos.refetch(),
|
|
92
|
+
})
|
|
93
|
+
);
|
|
94
|
+
{{/if}}
|
|
66
95
|
|
|
67
96
|
const handleAddTodo = (e: React.FormEvent) => {
|
|
68
97
|
e.preventDefault();
|
|
@@ -78,6 +107,7 @@ export default function Todos() {
|
|
|
78
107
|
const handleDeleteTodo = (id: number) => {
|
|
79
108
|
deleteMutation.mutate({ id });
|
|
80
109
|
};
|
|
110
|
+
{{/if}}
|
|
81
111
|
|
|
82
112
|
return (
|
|
83
113
|
<div className="w-full mx-auto max-w-md py-10">
|
|
@@ -95,62 +125,117 @@ export default function Todos() {
|
|
|
95
125
|
value={newTodoText}
|
|
96
126
|
onChange={(e) => setNewTodoText(e.target.value)}
|
|
97
127
|
placeholder="Add a new task..."
|
|
128
|
+
{{#if (eq backend "convex")}}
|
|
129
|
+
{{!-- Convex mutations don't have an easy isPending state here, disable based on text --}}
|
|
130
|
+
{{else}}
|
|
98
131
|
disabled={createMutation.isPending}
|
|
132
|
+
{{/if}}
|
|
99
133
|
/>
|
|
100
134
|
<Button
|
|
101
135
|
type="submit"
|
|
136
|
+
{{#if (eq backend "convex")}}
|
|
137
|
+
disabled={!newTodoText.trim()}
|
|
138
|
+
{{else}}
|
|
102
139
|
disabled={createMutation.isPending || !newTodoText.trim()}
|
|
140
|
+
{{/if}}
|
|
103
141
|
>
|
|
104
|
-
{
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
142
|
+
{{#if (eq backend "convex")}}
|
|
143
|
+
Add
|
|
144
|
+
{{else}}
|
|
145
|
+
{createMutation.isPending ? (
|
|
146
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
147
|
+
) : (
|
|
148
|
+
"Add"
|
|
149
|
+
)}
|
|
150
|
+
{{/if}}
|
|
109
151
|
</Button>
|
|
110
152
|
</form>
|
|
111
153
|
|
|
112
|
-
{
|
|
113
|
-
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
No todos yet. Add one above
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
154
|
+
{{#if (eq backend "convex")}}
|
|
155
|
+
{todos === undefined ? (
|
|
156
|
+
<div className="flex justify-center py-4">
|
|
157
|
+
<Loader2 className="h-6 w-6 animate-spin" />
|
|
158
|
+
</div>
|
|
159
|
+
) : todos.length === 0 ? (
|
|
160
|
+
<p className="py-4 text-center">No todos yet. Add one above!</p>
|
|
161
|
+
) : (
|
|
162
|
+
<ul className="space-y-2">
|
|
163
|
+
{todos.map((todo) => (
|
|
164
|
+
<li
|
|
165
|
+
key={todo._id}
|
|
166
|
+
className="flex items-center justify-between rounded-md border p-2"
|
|
167
|
+
>
|
|
168
|
+
<div className="flex items-center space-x-2">
|
|
169
|
+
<Checkbox
|
|
170
|
+
checked={todo.completed}
|
|
171
|
+
onCheckedChange={() =>
|
|
172
|
+
handleToggleTodo(todo._id, todo.completed)
|
|
173
|
+
}
|
|
174
|
+
id={`todo-${todo._id}`}
|
|
175
|
+
/>
|
|
176
|
+
<label
|
|
177
|
+
htmlFor={`todo-${todo._id}`}
|
|
178
|
+
className={`${todo.completed ? "line-through text-muted-foreground" : ""}`}
|
|
179
|
+
>
|
|
180
|
+
{todo.text}
|
|
181
|
+
</label>
|
|
182
|
+
</div>
|
|
183
|
+
<Button
|
|
184
|
+
variant="ghost"
|
|
185
|
+
size="icon"
|
|
186
|
+
onClick={() => handleDeleteTodo(todo._id)}
|
|
187
|
+
aria-label="Delete todo"
|
|
138
188
|
>
|
|
139
|
-
|
|
140
|
-
</
|
|
141
|
-
</
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
189
|
+
<Trash2 className="h-4 w-4" />
|
|
190
|
+
</Button>
|
|
191
|
+
</li>
|
|
192
|
+
))}
|
|
193
|
+
</ul>
|
|
194
|
+
)}
|
|
195
|
+
{{else}}
|
|
196
|
+
{todos.isLoading ? (
|
|
197
|
+
<div className="flex justify-center py-4">
|
|
198
|
+
<Loader2 className="h-6 w-6 animate-spin" />
|
|
199
|
+
</div>
|
|
200
|
+
) : todos.data?.length === 0 ? (
|
|
201
|
+
<p className="py-4 text-center">
|
|
202
|
+
No todos yet. Add one above!
|
|
203
|
+
</p>
|
|
204
|
+
) : (
|
|
205
|
+
<ul className="space-y-2">
|
|
206
|
+
{todos.data?.map((todo) => (
|
|
207
|
+
<li
|
|
208
|
+
key={todo.id}
|
|
209
|
+
className="flex items-center justify-between rounded-md border p-2"
|
|
147
210
|
>
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
211
|
+
<div className="flex items-center space-x-2">
|
|
212
|
+
<Checkbox
|
|
213
|
+
checked={todo.completed}
|
|
214
|
+
onCheckedChange={() =>
|
|
215
|
+
handleToggleTodo(todo.id, todo.completed)
|
|
216
|
+
}
|
|
217
|
+
id={`todo-${todo.id}`}
|
|
218
|
+
/>
|
|
219
|
+
<label
|
|
220
|
+
htmlFor={`todo-${todo.id}`}
|
|
221
|
+
className={`${todo.completed ? "line-through" : ""}`}
|
|
222
|
+
>
|
|
223
|
+
{todo.text}
|
|
224
|
+
</label>
|
|
225
|
+
</div>
|
|
226
|
+
<Button
|
|
227
|
+
variant="ghost"
|
|
228
|
+
size="icon"
|
|
229
|
+
onClick={() => handleDeleteTodo(todo.id)}
|
|
230
|
+
aria-label="Delete todo"
|
|
231
|
+
>
|
|
232
|
+
<Trash2 className="h-4 w-4" />
|
|
233
|
+
</Button>
|
|
234
|
+
</li>
|
|
235
|
+
))}
|
|
236
|
+
</ul>
|
|
237
|
+
)}
|
|
238
|
+
{{/if}}
|
|
154
239
|
</CardContent>
|
|
155
240
|
</Card>
|
|
156
241
|
</div>
|