create-better-t-stack 3.12.5 → 3.12.6

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 (35) hide show
  1. package/dist/cli.mjs +1 -1
  2. package/dist/index.mjs +1 -1
  3. package/dist/{src-D3EHRs6z.mjs → src-Ci2sRCrb.mjs} +31 -20
  4. package/package.json +2 -2
  5. package/templates/auth/better-auth/convex/native/uniwind/components/sign-in.tsx.hbs +32 -50
  6. package/templates/auth/better-auth/convex/native/uniwind/components/sign-up.tsx.hbs +40 -57
  7. package/templates/auth/better-auth/native/uniwind/components/sign-in.tsx.hbs +32 -35
  8. package/templates/auth/better-auth/native/uniwind/components/sign-up.tsx.hbs +49 -37
  9. package/templates/auth/better-auth/web/nuxt/app/components/SignInForm.vue.hbs +40 -35
  10. package/templates/auth/better-auth/web/nuxt/app/components/SignUpForm.vue.hbs +47 -40
  11. package/templates/auth/better-auth/web/nuxt/app/middleware/auth.ts.hbs +9 -7
  12. package/templates/auth/better-auth/web/nuxt/app/pages/dashboard.vue.hbs +61 -29
  13. package/templates/auth/better-auth/web/nuxt/app/pages/login.vue.hbs +6 -3
  14. package/templates/backend/server/elysia/src/index.ts.hbs +8 -3
  15. package/templates/backend/server/express/src/index.ts.hbs +8 -3
  16. package/templates/backend/server/fastify/src/index.ts.hbs +8 -3
  17. package/templates/backend/server/hono/src/index.ts.hbs +16 -6
  18. package/templates/examples/ai/fullstack/next/src/app/api/ai/route.ts.hbs +8 -3
  19. package/templates/examples/ai/fullstack/tanstack-start/src/routes/api/ai/$.ts.hbs +8 -3
  20. package/templates/examples/ai/native/bare/polyfills.js +14 -11
  21. package/templates/examples/ai/native/uniwind/app/(drawer)/ai.tsx.hbs +104 -124
  22. package/templates/examples/ai/web/nuxt/app/pages/ai.vue.hbs +22 -22
  23. package/templates/examples/todo/native/uniwind/app/(drawer)/todos.tsx.hbs +67 -80
  24. package/templates/examples/todo/web/nuxt/app/pages/todos.vue.hbs +115 -90
  25. package/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs +60 -54
  26. package/templates/frontend/native/uniwind/app/+not-found.tsx.hbs +16 -21
  27. package/templates/frontend/native/uniwind/app/modal.tsx.hbs +18 -34
  28. package/templates/frontend/native/uniwind/package.json.hbs +10 -10
  29. package/templates/frontend/nuxt/app/components/Header.vue.hbs +25 -29
  30. package/templates/frontend/nuxt/app/layouts/default.vue.hbs +7 -8
  31. package/templates/frontend/nuxt/app/pages/index.vue.hbs +58 -39
  32. package/templates/frontend/nuxt/package.json.hbs +1 -1
  33. package/templates/frontend/react/web-base/src/components/ui/skeleton.tsx.hbs +5 -5
  34. package/templates/frontend/nuxt/app/components/Loader.vue.hbs +0 -5
  35. package/templates/frontend/nuxt/app/components/ModeToggle.vue.hbs +0 -25
@@ -1,13 +1,5 @@
1
1
  import { useState } from "react";
2
- import {
3
- View,
4
- Text,
5
- TextInput,
6
- ScrollView,
7
- ActivityIndicator,
8
- Alert,
9
- Pressable,
10
- } from "react-native";
2
+ import { View, Text, ScrollView, Alert } from "react-native";
11
3
  import { Ionicons } from "@expo/vector-icons";
12
4
  {{#if (eq backend "convex")}}
13
5
  import { useMutation, useQuery } from "convex/react";
@@ -25,7 +17,7 @@ import { Container } from "@/components/container";
25
17
  import { trpc } from "@/utils/trpc";
26
18
  {{/if}}
27
19
  {{/unless}}
28
- import { Card, Checkbox, useThemeColor, Chip } from "heroui-native";
20
+ import { Button, Checkbox, Chip, Spinner, Surface, TextField, useThemeColor } from "heroui-native";
29
21
 
30
22
  export default function TodosScreen() {
31
23
  const [newTodoText, setNewTodoText] = useState("");
@@ -76,7 +68,6 @@ export default function TodosScreen() {
76
68
  {{/if}}
77
69
 
78
70
  const mutedColor = useThemeColor("muted");
79
- const accentColor = useThemeColor("accent");
80
71
  const dangerColor = useThemeColor("danger");
81
72
  const foregroundColor = useThemeColor("foreground");
82
73
 
@@ -135,12 +126,10 @@ export default function TodosScreen() {
135
126
 
136
127
  return (
137
128
  <Container>
138
- <ScrollView className="flex-1" contentContainerClassName="p-6">
139
- <View className="mb-6">
140
- <View className="flex-row items-center justify-between mb-2">
141
- <Text className="text-3xl font-bold text-foreground">
142
- Todo List
143
- </Text>
129
+ <ScrollView className="flex-1" contentContainerClassName="p-4">
130
+ <View className="py-4 mb-4">
131
+ <View className="flex-row items-center justify-between">
132
+ <Text className="text-2xl font-semibold text-foreground tracking-tight">Tasks</Text>
144
133
  {totalCount > 0 && (
145
134
  <Chip variant="secondary" color="accent" size="sm">
146
135
  <Chip.Label>
@@ -151,140 +140,138 @@ export default function TodosScreen() {
151
140
  </View>
152
141
  </View>
153
142
 
154
- <Card variant="secondary" className="mb-6 p-4">
155
- <View className="flex-row items-center gap-3">
143
+ <Surface variant="secondary" className="mb-4 p-3 rounded-lg">
144
+ <View className="flex-row items-center gap-2">
156
145
  <View className="flex-1">
157
- <TextInput
158
- value={newTodoText}
159
- onChangeText={setNewTodoText}
160
- placeholder="Add a new task..."
161
- placeholderTextColor={mutedColor}
162
- {{#unless (eq backend "convex")}}
163
- editable={!createMutation.isPending}
164
- {{/unless}}
165
- onSubmitEditing={handleAddTodo}
166
- returnKeyType="done"
167
- className="text-foreground text-base py-3 px-4 border border-divider rounded-lg bg-surface"
168
- />
146
+ <TextField>
147
+ <TextField.Input
148
+ value={newTodoText}
149
+ onChangeText={setNewTodoText}
150
+ placeholder="Add a new task..."
151
+ {{#unless (eq backend "convex")}}
152
+ editable={!createMutation.isPending}
153
+ {{/unless}}
154
+ onSubmitEditing={handleAddTodo}
155
+ returnKeyType="done"
156
+ />
157
+ </TextField>
169
158
  </View>
170
- <Pressable
171
- onPress={handleAddTodo}
159
+ <Button
160
+ isIconOnly
172
161
  {{#if (eq backend "convex")}}
173
- disabled={!newTodoText.trim()}
174
- className={`p-3 rounded-lg active:opacity-70 ${newTodoText.trim() ? "bg-accent" : "bg-surface"}`}
162
+ variant={!newTodoText.trim() ? "secondary" : "primary"}
163
+ isDisabled={!newTodoText.trim()}
175
164
  {{else}}
176
- disabled={createMutation.isPending || !newTodoText.trim()}
177
- className={`p-3 rounded-lg active:opacity-70 ${(createMutation.isPending || !newTodoText.trim()) ? "bg-surface" : "bg-accent"}`}
165
+ variant={createMutation.isPending || !newTodoText.trim() ? "secondary" : "primary"}
166
+ isDisabled={createMutation.isPending || !newTodoText.trim()}
178
167
  {{/if}}
168
+ onPress={handleAddTodo}
169
+ size="sm"
179
170
  >
180
171
  {{#if (eq backend "convex")}}
181
172
  <Ionicons
182
173
  name="add"
183
- size={24}
174
+ size={20}
184
175
  color={newTodoText.trim() ? foregroundColor : mutedColor}
185
176
  />
186
177
  {{else}}
187
178
  {createMutation.isPending ? (
188
- <ActivityIndicator size="small" color={foregroundColor} />
179
+ <Spinner size="sm" color="default" />
189
180
  ) : (
190
181
  <Ionicons
191
182
  name="add"
192
- size={24}
183
+ size={20}
193
184
  color={(createMutation.isPending || !newTodoText.trim()) ? mutedColor : foregroundColor}
194
185
  />
195
186
  )}
196
187
  {{/if}}
197
- </Pressable>
188
+ </Button>
198
189
  </View>
199
- </Card>
190
+ </Surface>
200
191
 
201
192
  {{#if (eq backend "convex")}}
202
193
  {isLoading && (
203
194
  <View className="items-center justify-center py-12">
204
- <ActivityIndicator size="large" color={accentColor} />
205
- <Text className="text-muted mt-4">Loading todos...</Text>
195
+ <Spinner size="lg" />
196
+ <Text className="text-muted text-sm mt-3">Loading tasks...</Text>
206
197
  </View>
207
198
  )}
208
199
 
209
200
  {todos && todos.length === 0 && !isLoading && (
210
- <Card className="items-center justify-center py-12">
211
- <Ionicons name="checkbox-outline" size={64} color={mutedColor} style=\{{ marginBottom: 16 }} />
212
- <Text className="text-foreground text-lg font-semibold mb-2">
213
- No todos yet
214
- </Text>
215
- <Text className="text-muted text-center">
216
- Add your first task to get started!
217
- </Text>
218
- </Card>
201
+ <Surface variant="secondary" className="items-center justify-center py-10 rounded-lg">
202
+ <Ionicons name="checkbox-outline" size={40} color={mutedColor} />
203
+ <Text className="text-foreground font-medium mt-3">No tasks yet</Text>
204
+ <Text className="text-muted text-xs mt-1">Add your first task to get started</Text>
205
+ </Surface>
219
206
  )}
220
207
 
221
208
  {todos && todos.length > 0 && (
222
- <View className="gap-3">
209
+ <View className="gap-2">
223
210
  {todos.map((todo) => (
224
- <Card key={todo._id} variant="secondary" className="p-4">
211
+ <Surface key={todo._id} variant="secondary" className="p-3 rounded-lg">
225
212
  <View className="flex-row items-center gap-3">
226
213
  <Checkbox
227
214
  isSelected={todo.completed}
228
215
  onSelectedChange={() => handleToggleTodo(todo._id, todo.completed)}
229
216
  />
230
217
  <View className="flex-1">
231
- <Text className={`text-base ${todo.completed ? "text-muted line-through" : "text-foreground"}`}>
218
+ <Text className={`text-sm ${todo.completed ? "text-muted line-through" : "text-foreground"}`}>
232
219
  {todo.text}
233
220
  </Text>
234
221
  </View>
235
- <Pressable
222
+ <Button
223
+ isIconOnly
224
+ variant="ghost"
236
225
  onPress={() => handleDeleteTodo(todo._id)}
237
- className="p-2 rounded-lg active:opacity-70"
226
+ size="sm"
238
227
  >
239
- <Ionicons name="trash-outline" size={24} color={dangerColor} />
240
- </Pressable>
228
+ <Ionicons name="trash-outline" size={16} color={dangerColor} />
229
+ </Button>
241
230
  </View>
242
- </Card>
231
+ </Surface>
243
232
  ))}
244
233
  </View>
245
234
  )}
246
235
  {{else}}
247
236
  {isLoading && (
248
237
  <View className="items-center justify-center py-12">
249
- <ActivityIndicator size="large" color={accentColor} />
250
- <Text className="text-muted mt-4">Loading todos...</Text>
238
+ <Spinner size="lg" />
239
+ <Text className="text-muted text-sm mt-3">Loading tasks...</Text>
251
240
  </View>
252
241
  )}
253
242
 
254
243
  {todos?.data && todos.data.length === 0 && !isLoading && (
255
- <Card className="items-center justify-center py-12">
256
- <Ionicons name="checkbox-outline" size={64} color={mutedColor} style=\{{ marginBottom: 16 }} />
257
- <Text className="text-foreground text-lg font-semibold mb-2">
258
- No todos yet
259
- </Text>
260
- <Text className="text-muted text-center">
261
- Add your first task to get started!
262
- </Text>
263
- </Card>
244
+ <Surface variant="secondary" className="items-center justify-center py-10 rounded-lg">
245
+ <Ionicons name="checkbox-outline" size={40} color={mutedColor} />
246
+ <Text className="text-foreground font-medium mt-3">No tasks yet</Text>
247
+ <Text className="text-muted text-xs mt-1">Add your first task to get started</Text>
248
+ </Surface>
264
249
  )}
265
250
 
266
251
  {todos?.data && todos.data.length > 0 && (
267
- <View className="gap-3">
252
+ <View className="gap-2">
268
253
  {todos.data.map((todo) => (
269
- <Card key={todo.id} variant="secondary" className="p-4">
254
+ <Surface key={todo.id} variant="secondary" className="p-3 rounded-lg">
270
255
  <View className="flex-row items-center gap-3">
271
256
  <Checkbox
272
257
  isSelected={todo.completed}
273
258
  onSelectedChange={() => handleToggleTodo(todo.id, todo.completed)}
274
259
  />
275
260
  <View className="flex-1">
276
- <Text className={`text-base ${todo.completed ? "text-muted line-through" : "text-foreground"}`}>
261
+ <Text className={`text-sm ${todo.completed ? "text-muted line-through" : "text-foreground"}`}>
277
262
  {todo.text}
278
263
  </Text>
279
264
  </View>
280
- <Pressable
265
+ <Button
266
+ isIconOnly
267
+ variant="ghost"
281
268
  onPress={() => handleDeleteTodo(todo.id)}
282
- className="p-2 rounded-lg active:opacity-70"
269
+ size="sm"
283
270
  >
284
- <Ionicons name="trash-outline" size={24} color={dangerColor} />
285
- </Pressable>
271
+ <Ionicons name="trash-outline" size={16} color={dangerColor} />
272
+ </Button>
286
273
  </View>
287
- </Card>
274
+ </Surface>
288
275
  ))}
289
276
  </View>
290
277
  )}
@@ -72,7 +72,7 @@ function handleDeleteTodo(id: number) {
72
72
  </script>
73
73
 
74
74
  <template>
75
- <div class="mx-auto w-full max-w-md py-10">
75
+ <UContainer class="py-8 max-w-md">
76
76
  <UCard>
77
77
  <template #header>
78
78
  <div>
@@ -80,12 +80,13 @@ function handleDeleteTodo(id: number) {
80
80
  <div class="text-muted text-sm">Manage your tasks efficiently</div>
81
81
  </div>
82
82
  </template>
83
+
83
84
  <form @submit.prevent="handleAddTodo" class="mb-6 flex items-center gap-2">
84
85
  <UInput
85
86
  v-model="newTodoText"
86
87
  placeholder="Add a new task..."
87
88
  autocomplete="off"
88
- class="w-full"
89
+ class="flex-1"
89
90
  {{#if (eq backend "convex")}}
90
91
  :disabled="isCreatePending"
91
92
  {{/if}}
@@ -93,103 +94,127 @@ function handleDeleteTodo(id: number) {
93
94
  <UButton
94
95
  type="submit"
95
96
  {{#if (eq backend "convex")}}
96
- :disabled="isCreatePending || !newTodoText.trim()"
97
- {{/if}}
98
- >
99
- {{#if (eq backend "convex")}}
100
- <span v-if="isCreatePending">
101
- <UIcon name="i-lucide-loader-2" class="animate-spin" />
102
- </span>
103
- <span v-else>Add</span>
97
+ :loading="isCreatePending"
98
+ :disabled="!newTodoText.trim()"
104
99
  {{else}}
105
- Add
100
+ :loading="createMutation.isPending.value"
106
101
  {{/if}}
102
+ >
103
+ Add
107
104
  </UButton>
108
105
  </form>
109
106
 
110
107
  {{#if (eq backend "convex")}}
111
- <p v-if="error || deleteError" class="mb-4 text-red-500">
112
- Error: \{{ error?.message || deleteError?.message }}
113
- </p>
114
- <div v-if="isPending" class="flex justify-center py-4">
115
- <UIcon name="i-lucide-loader-2" class="animate-spin w-6 h-6" />
116
- </div>
117
- <p v-else-if="data?.length === 0" class="py-4 text-center">
118
- No todos yet. Add one above!
119
- </p>
120
- <ul v-else-if="data" class="space-y-2">
121
- <li
122
- v-for="todo in data"
123
- :key="todo._id"
124
- class="flex items-center justify-between rounded-md border p-2"
125
- >
126
- <div class="flex items-center gap-2">
127
- <UCheckbox
128
- :model-value="todo.completed"
129
- @update:model-value="() => handleToggleTodo(todo._id, todo.completed)"
130
- :id="`todo-${todo._id}`"
131
- />
132
- <label
133
- :for="`todo-${todo._id}`"
134
- :class="{ 'line-through text-muted': todo.completed }"
135
- class="cursor-pointer"
136
- >
137
- \{{ todo.text }}
138
- </label>
139
- </div>
140
- <UButton
141
- color="neutral"
142
- variant="ghost"
143
- size="sm"
144
- square
145
- @click="handleDeleteTodo(todo._id)"
146
- aria-label="Delete todo"
147
- icon="i-lucide-trash-2"
108
+ <!-- Loading State -->
109
+ <div v-if="isPending" class="space-y-2">
110
+ <USkeleton v-for="i in 3" :key="i" class="h-12 w-full" />
111
+ </div>
112
+
113
+ <!-- Error State -->
114
+ <UAlert
115
+ v-else-if="error || deleteError"
116
+ color="error"
117
+ icon="i-lucide-alert-circle"
118
+ title="Error"
119
+ :description="error?.message || deleteError?.message"
120
+ />
121
+
122
+ <!-- Empty State -->
123
+ <UEmpty
124
+ v-else-if="data?.length === 0"
125
+ icon="i-lucide-clipboard-list"
126
+ title="No todos yet"
127
+ description="Add your first task above!"
128
+ />
129
+
130
+ <!-- Todo List -->
131
+ <ul v-else-if="data" class="space-y-2">
132
+ <li
133
+ v-for="todo in data"
134
+ :key="todo._id"
135
+ class="flex items-center justify-between rounded-md border p-3"
136
+ >
137
+ <div class="flex items-center gap-3">
138
+ <UCheckbox
139
+ :model-value="todo.completed"
140
+ @update:model-value="() => handleToggleTodo(todo._id, todo.completed)"
141
+ :id="`todo-${todo._id}`"
148
142
  />
149
- </li>
150
- </ul>
143
+ <label
144
+ :for="`todo-${todo._id}`"
145
+ :class="{ 'line-through text-muted': todo.completed }"
146
+ class="cursor-pointer"
147
+ >
148
+ \{{ todo.text }}
149
+ </label>
150
+ </div>
151
+ <UButton
152
+ color="error"
153
+ variant="ghost"
154
+ size="sm"
155
+ square
156
+ @click="handleDeleteTodo(todo._id)"
157
+ aria-label="Delete todo"
158
+ icon="i-lucide-trash-2"
159
+ />
160
+ </li>
161
+ </ul>
151
162
  {{else}}
152
- <div v-if="todos.status.value === 'pending'" class="flex justify-center py-4">
153
- <UIcon name="i-lucide-loader-2" class="animate-spin w-6 h-6" />
154
- </div>
155
- <p v-else-if="todos.status.value === 'error'" class="py-4 text-center text-red-500">
156
- Error: \{{ todos.error.value?.message || 'Failed to load todos' }}
157
- </p>
158
- <p v-else-if="todos.data.value?.length === 0" class="py-4 text-center">
159
- No todos yet. Add one above!
160
- </p>
161
- <ul v-else class="space-y-2">
162
- <li
163
- v-for="todo in todos.data.value"
164
- :key="todo.id"
165
- class="flex items-center justify-between rounded-md border p-2"
166
- >
167
- <div class="flex items-center gap-2">
168
- <UCheckbox
169
- :model-value="todo.completed"
170
- @update:model-value="() => handleToggleTodo(todo.id, todo.completed)"
171
- :id="`todo-${todo.id}`"
172
- />
173
- <label
174
- :for="`todo-${todo.id}`"
175
- :class="{ 'line-through text-muted': todo.completed }"
176
- class="cursor-pointer"
177
- >
178
- \{{ todo.text }}
179
- </label>
180
- </div>
181
- <UButton
182
- color="neutral"
183
- variant="ghost"
184
- size="sm"
185
- square
186
- @click="handleDeleteTodo(todo.id)"
187
- aria-label="Delete todo"
188
- icon="i-lucide-trash-2"
163
+ <!-- Loading State -->
164
+ <div v-if="todos.status.value === 'pending'" class="space-y-2">
165
+ <USkeleton v-for="i in 3" :key="i" class="h-12 w-full" />
166
+ </div>
167
+
168
+ <!-- Error State -->
169
+ <UAlert
170
+ v-else-if="todos.status.value === 'error'"
171
+ color="error"
172
+ icon="i-lucide-alert-circle"
173
+ title="Failed to load todos"
174
+ :description="todos.error.value?.message"
175
+ />
176
+
177
+ <!-- Empty State -->
178
+ <UEmpty
179
+ v-else-if="todos.data.value?.length === 0"
180
+ icon="i-lucide-clipboard-list"
181
+ title="No todos yet"
182
+ description="Add your first task above!"
183
+ />
184
+
185
+ <!-- Todo List -->
186
+ <ul v-else class="space-y-2">
187
+ <li
188
+ v-for="todo in todos.data.value"
189
+ :key="todo.id"
190
+ class="flex items-center justify-between rounded-md border p-3"
191
+ >
192
+ <div class="flex items-center gap-3">
193
+ <UCheckbox
194
+ :model-value="todo.completed"
195
+ @update:model-value="() => handleToggleTodo(todo.id, todo.completed)"
196
+ :id="`todo-${todo.id}`"
189
197
  />
190
- </li>
191
- </ul>
198
+ <label
199
+ :for="`todo-${todo.id}`"
200
+ :class="{ 'line-through text-muted': todo.completed }"
201
+ class="cursor-pointer"
202
+ >
203
+ \{{ todo.text }}
204
+ </label>
205
+ </div>
206
+ <UButton
207
+ color="error"
208
+ variant="ghost"
209
+ size="sm"
210
+ square
211
+ @click="handleDeleteTodo(todo.id)"
212
+ aria-label="Delete todo"
213
+ icon="i-lucide-trash-2"
214
+ />
215
+ </li>
216
+ </ul>
192
217
  {{/if}}
193
218
  </UCard>
194
- </div>
219
+ </UContainer>
195
220
  </template>