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.
Files changed (48) hide show
  1. package/dist/index.js +74 -128
  2. package/package.json +4 -4
  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/dist/index.d.ts +0 -2
  36. /package/templates/backend/{elysia → server/elysia}/src/index.ts.hbs +0 -0
  37. /package/templates/backend/{express → server/express}/src/index.ts.hbs +0 -0
  38. /package/templates/backend/{hono → server/hono}/src/index.ts.hbs +0 -0
  39. /package/templates/backend/{next → server/next}/next-env.d.ts +0 -0
  40. /package/templates/backend/{next → server/next}/next.config.ts +0 -0
  41. /package/templates/backend/{next → server/next}/package.json +0 -0
  42. /package/templates/backend/{next → server/next}/src/app/route.ts +0 -0
  43. /package/templates/backend/{next → server/next}/src/middleware.ts +0 -0
  44. /package/templates/backend/{next → server/next}/tsconfig.json +0 -0
  45. /package/templates/backend/{server-base → server/server-base}/_gitignore +0 -0
  46. /package/templates/backend/{server-base → server/server-base}/package.json +0 -0
  47. /package/templates/backend/{server-base → server/server-base}/src/routers/index.ts.hbs +0 -0
  48. /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.2.3",
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": "tsup",
49
- "dev": "tsup --watch",
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
- "tsup": "^8.4.0",
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,
@@ -1,6 +1,5 @@
1
1
  <script setup lang="ts">
2
2
  import { z } from 'zod'
3
- // import { authClient } from "~/lib/auth-client";
4
3
  const {$authClient} = useNuxtApp()
5
4
  import type { FormSubmitEvent } from '#ui/types'
6
5
 
@@ -1,7 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { z } from 'zod'
3
3
  import type { FormSubmitEvent } from '#ui/types'
4
- // import { authClient } from "~/lib/auth-client";
5
4
  const {$authClient} = useNuxtApp()
6
5
 
7
6
  const emit = defineEmits(['switchToSignIn'])
@@ -1,6 +1,5 @@
1
1
  <script setup lang="ts">
2
2
 
3
- // import { authClient } from "~/lib/auth-client";
4
3
  const {$authClient} = useNuxtApp()
5
4
  const session = $authClient.useSession()
6
5
  const toast = useToast()
@@ -0,0 +1,2 @@
1
+
2
+ .env.local
@@ -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,7 @@
1
+ import { query } from "./_generated/server";
2
+
3
+ export const get = query({
4
+ handler: async () => {
5
+ return "OK";
6
+ }
7
+ })
@@ -0,0 +1,9 @@
1
+ import { defineSchema, defineTable } from "convex/server";
2
+ import { v } from "convex/values";
3
+
4
+ export default defineSchema({
5
+ todos: defineTable({
6
+ text: v.string(),
7
+ completed: v.boolean(),
8
+ }),
9
+ });
@@ -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>