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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-better-t-stack",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
},
|
|
46
46
|
"homepage": "https://better-t-stack.amanv.dev/",
|
|
47
47
|
"scripts": {
|
|
48
|
-
"build": "
|
|
49
|
-
"dev": "
|
|
48
|
+
"build": "tsdown",
|
|
49
|
+
"dev": "tsdown --watch",
|
|
50
50
|
"check-types": "tsc --noEmit",
|
|
51
51
|
"check": "biome check --write .",
|
|
52
52
|
"test": "vitest run",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"@types/globby": "^9.1.0",
|
|
69
69
|
"@types/node": "^22.14.1",
|
|
70
70
|
"@types/yargs": "^17.0.33",
|
|
71
|
-
"
|
|
71
|
+
"tsdown": "^0.9.9",
|
|
72
72
|
"typescript": "^5.8.3"
|
|
73
73
|
}
|
|
74
74
|
}
|
|
@@ -16,7 +16,12 @@
|
|
|
16
16
|
"dev": {
|
|
17
17
|
"cache": false,
|
|
18
18
|
"persistent": true
|
|
19
|
-
},
|
|
19
|
+
}{{#if (eq backend "convex")}},
|
|
20
|
+
"setup": {
|
|
21
|
+
"cache": false,
|
|
22
|
+
"persistent": true
|
|
23
|
+
}
|
|
24
|
+
{{else}}{{#unless (or (eq database "none") (eq orm "none"))}},
|
|
20
25
|
"db:push": {
|
|
21
26
|
"cache": false,
|
|
22
27
|
"persistent": true
|
|
@@ -25,5 +30,6 @@
|
|
|
25
30
|
"cache": false,
|
|
26
31
|
"persistent": true
|
|
27
32
|
}
|
|
33
|
+
{{/unless}}{{/if}}
|
|
28
34
|
}
|
|
29
35
|
}
|
|
@@ -92,8 +92,6 @@ export async function createContext(opts: any) {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
{{else}}
|
|
95
|
-
// Default or fallback context if backend is not recognized or none
|
|
96
|
-
// This might need adjustment based on your default behavior
|
|
97
95
|
export async function createContext() {
|
|
98
96
|
return {
|
|
99
97
|
session: null,
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Welcome to your Convex functions directory!
|
|
2
|
+
|
|
3
|
+
Write your Convex functions here.
|
|
4
|
+
See https://docs.convex.dev/functions for more.
|
|
5
|
+
|
|
6
|
+
A query function that takes two arguments looks like:
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
// functions.js
|
|
10
|
+
import { query } from "./_generated/server";
|
|
11
|
+
import { v } from "convex/values";
|
|
12
|
+
|
|
13
|
+
export const myQueryFunction = query({
|
|
14
|
+
// Validators for arguments.
|
|
15
|
+
args: {
|
|
16
|
+
first: v.number(),
|
|
17
|
+
second: v.string(),
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
// Function implementation.
|
|
21
|
+
handler: async (ctx, args) => {
|
|
22
|
+
// Read the database as many times as you need here.
|
|
23
|
+
// See https://docs.convex.dev/database/reading-data.
|
|
24
|
+
const documents = await ctx.db.query("tablename").collect();
|
|
25
|
+
|
|
26
|
+
// Arguments passed from the client are properties of the args object.
|
|
27
|
+
console.log(args.first, args.second);
|
|
28
|
+
|
|
29
|
+
// Write arbitrary JavaScript here: filter, aggregate, build derived data,
|
|
30
|
+
// remove non-public properties, or create new objects.
|
|
31
|
+
return documents;
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Using this query function in a React component looks like:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
const data = useQuery(api.functions.myQueryFunction, {
|
|
40
|
+
first: 10,
|
|
41
|
+
second: "hello",
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
A mutation function looks like:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
// functions.js
|
|
49
|
+
import { mutation } from "./_generated/server";
|
|
50
|
+
import { v } from "convex/values";
|
|
51
|
+
|
|
52
|
+
export const myMutationFunction = mutation({
|
|
53
|
+
// Validators for arguments.
|
|
54
|
+
args: {
|
|
55
|
+
first: v.string(),
|
|
56
|
+
second: v.string(),
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
// Function implementation.
|
|
60
|
+
handler: async (ctx, args) => {
|
|
61
|
+
// Insert or modify documents in the database here.
|
|
62
|
+
// Mutations can also read from the database like queries.
|
|
63
|
+
// See https://docs.convex.dev/database/writing-data.
|
|
64
|
+
const message = { body: args.first, author: args.second };
|
|
65
|
+
const id = await ctx.db.insert("messages", message);
|
|
66
|
+
|
|
67
|
+
// Optionally, return a value from your mutation.
|
|
68
|
+
return await ctx.db.get(id);
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Using this mutation function in a React component looks like:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const mutation = useMutation(api.functions.myMutationFunction);
|
|
77
|
+
function handleButtonPress() {
|
|
78
|
+
// fire and forget, the most common way to use mutations
|
|
79
|
+
mutation({ first: "Hello!", second: "me" });
|
|
80
|
+
// OR
|
|
81
|
+
// use the result once the mutation has completed
|
|
82
|
+
mutation({ first: "Hello!", second: "me" }).then((result) =>
|
|
83
|
+
console.log(result),
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Use the Convex CLI to push your functions to a deployment. See everything
|
|
89
|
+
the Convex CLI can do by running `npx convex -h` in your project root
|
|
90
|
+
directory. To learn more, launch the docs with `npx convex docs`.
|
|
@@ -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>
|