create-better-t-stack 2.2.4 → 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.
Files changed (47) 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/extras/pnpm-workspace.yaml +1 -0
  20. package/templates/frontend/native/app/(drawer)/index.tsx.hbs +35 -7
  21. package/templates/frontend/native/app/_layout.tsx.hbs +27 -0
  22. package/templates/frontend/react/next/package.json +0 -2
  23. package/templates/frontend/react/next/src/app/page.tsx.hbs +26 -44
  24. package/templates/frontend/react/next/src/components/providers.tsx.hbs +15 -3
  25. package/templates/frontend/react/react-router/package.json +0 -2
  26. package/templates/frontend/react/react-router/src/root.tsx.hbs +31 -11
  27. package/templates/frontend/react/react-router/src/routes/_index.tsx.hbs +28 -47
  28. package/templates/frontend/react/tanstack-router/package.json +0 -2
  29. package/templates/frontend/react/tanstack-router/src/main.tsx.hbs +18 -2
  30. package/templates/frontend/react/tanstack-router/src/routes/__root.tsx.hbs +24 -1
  31. package/templates/frontend/react/tanstack-router/src/routes/index.tsx.hbs +24 -39
  32. package/templates/frontend/react/tanstack-start/src/router.tsx.hbs +57 -13
  33. package/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs +12 -10
  34. package/templates/frontend/react/tanstack-start/src/routes/index.tsx.hbs +31 -45
  35. /package/templates/backend/{elysia → server/elysia}/src/index.ts.hbs +0 -0
  36. /package/templates/backend/{express → server/express}/src/index.ts.hbs +0 -0
  37. /package/templates/backend/{hono → server/hono}/src/index.ts.hbs +0 -0
  38. /package/templates/backend/{next → server/next}/next-env.d.ts +0 -0
  39. /package/templates/backend/{next → server/next}/next.config.ts +0 -0
  40. /package/templates/backend/{next → server/next}/package.json +0 -0
  41. /package/templates/backend/{next → server/next}/src/app/route.ts +0 -0
  42. /package/templates/backend/{next → server/next}/src/middleware.ts +0 -0
  43. /package/templates/backend/{next → server/next}/tsconfig.json +0 -0
  44. /package/templates/backend/{server-base → server/server-base}/_gitignore +0 -0
  45. /package/templates/backend/{server-base → server/server-base}/package.json +0 -0
  46. /package/templates/backend/{server-base → server/server-base}/src/routers/index.ts.hbs +0 -0
  47. /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
+ }
@@ -2,7 +2,8 @@
2
2
  "name": "better-t-stack",
3
3
  "private": true,
4
4
  "workspaces": [
5
- "apps/*"
5
+ "apps/*",
6
+ "packages/*"
6
7
  ],
7
8
  "scripts": {
8
9
 
@@ -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
- export default function Todos() {
22
- const [newTodoText, setNewTodoText] = useState("");
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
- const todos = useQuery(orpc.todo.getAll.queryOptions());
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
- const todos = useQuery(trpc.todo.getAll.queryOptions());
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
- {createMutation.isPending ? (
105
- <Loader2 className="h-4 w-4 animate-spin" />
106
- ) : (
107
- "Add"
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
- {todos.isLoading ? (
113
- <div className="flex justify-center py-4">
114
- <Loader2 className="h-6 w-6 animate-spin" />
115
- </div>
116
- ) : todos.data?.length === 0 ? (
117
- <p className="py-4 text-center">
118
- No todos yet. Add one above!
119
- </p>
120
- ) : (
121
- <ul className="space-y-2">
122
- {todos.data?.map((todo) => (
123
- <li
124
- key={todo.id}
125
- className="flex items-center justify-between rounded-md border p-2"
126
- >
127
- <div className="flex items-center space-x-2">
128
- <Checkbox
129
- checked={todo.completed}
130
- onCheckedChange={() =>
131
- handleToggleTodo(todo.id, todo.completed)
132
- }
133
- id={`todo-${todo.id}`}
134
- />
135
- <label
136
- htmlFor={`todo-${todo.id}`}
137
- className={`${todo.completed ? "line-through" : ""}`}
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
- {todo.text}
140
- </label>
141
- </div>
142
- <Button
143
- variant="ghost"
144
- size="icon"
145
- onClick={() => handleDeleteTodo(todo.id)}
146
- aria-label="Delete todo"
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
- <Trash2 className="h-4 w-4" />
149
- </Button>
150
- </li>
151
- ))}
152
- </ul>
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>