create-better-t-stack 3.12.5 → 3.12.7

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 (37) hide show
  1. package/dist/cli.mjs +1 -1
  2. package/dist/index.mjs +1 -1
  3. package/dist/{src-D3EHRs6z.mjs → src-DNjxspj9.mjs} +32 -21
  4. package/package.json +2 -2
  5. package/templates/api/orpc/web/react/base/src/utils/orpc.ts.hbs +3 -3
  6. package/templates/auth/better-auth/convex/native/uniwind/components/sign-in.tsx.hbs +32 -50
  7. package/templates/auth/better-auth/convex/native/uniwind/components/sign-up.tsx.hbs +40 -57
  8. package/templates/auth/better-auth/native/uniwind/components/sign-in.tsx.hbs +32 -35
  9. package/templates/auth/better-auth/native/uniwind/components/sign-up.tsx.hbs +49 -37
  10. package/templates/auth/better-auth/web/nuxt/app/components/SignInForm.vue.hbs +40 -35
  11. package/templates/auth/better-auth/web/nuxt/app/components/SignUpForm.vue.hbs +47 -40
  12. package/templates/auth/better-auth/web/nuxt/app/middleware/auth.ts.hbs +9 -7
  13. package/templates/auth/better-auth/web/nuxt/app/pages/dashboard.vue.hbs +61 -29
  14. package/templates/auth/better-auth/web/nuxt/app/pages/login.vue.hbs +6 -3
  15. package/templates/backend/server/elysia/src/index.ts.hbs +8 -3
  16. package/templates/backend/server/express/src/index.ts.hbs +8 -3
  17. package/templates/backend/server/fastify/src/index.ts.hbs +8 -3
  18. package/templates/backend/server/hono/src/index.ts.hbs +16 -6
  19. package/templates/examples/ai/fullstack/next/src/app/api/ai/route.ts.hbs +8 -3
  20. package/templates/examples/ai/fullstack/tanstack-start/src/routes/api/ai/$.ts.hbs +8 -3
  21. package/templates/examples/ai/native/bare/polyfills.js +14 -11
  22. package/templates/examples/ai/native/uniwind/app/(drawer)/ai.tsx.hbs +104 -124
  23. package/templates/examples/ai/web/nuxt/app/pages/ai.vue.hbs +22 -22
  24. package/templates/examples/todo/native/uniwind/app/(drawer)/todos.tsx.hbs +67 -80
  25. package/templates/examples/todo/web/nuxt/app/pages/todos.vue.hbs +115 -90
  26. package/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs +60 -54
  27. package/templates/frontend/native/uniwind/app/+not-found.tsx.hbs +16 -21
  28. package/templates/frontend/native/uniwind/app/modal.tsx.hbs +18 -34
  29. package/templates/frontend/native/uniwind/package.json.hbs +10 -10
  30. package/templates/frontend/nuxt/app/components/Header.vue.hbs +25 -29
  31. package/templates/frontend/nuxt/app/layouts/default.vue.hbs +7 -8
  32. package/templates/frontend/nuxt/app/pages/index.vue.hbs +58 -39
  33. package/templates/frontend/nuxt/package.json.hbs +1 -1
  34. package/templates/frontend/react/tanstack-router/package.json.hbs +1 -1
  35. package/templates/frontend/react/web-base/src/components/ui/skeleton.tsx.hbs +5 -5
  36. package/templates/frontend/nuxt/app/components/Loader.vue.hbs +0 -5
  37. package/templates/frontend/nuxt/app/components/ModeToggle.vue.hbs +0 -25
@@ -7,17 +7,14 @@ import {
7
7
  } from "@convex-dev/agent/react";
8
8
  import { api } from "@{{projectName}}/backend/convex/_generated/api";
9
9
  import { useMutation } from "convex/react";
10
- import { Card, useThemeColor } from "heroui-native";
10
+ import { Button, Divider, Spinner, Surface, TextField, useThemeColor } from "heroui-native";
11
11
  import { useRef, useEffect, useState } from "react";
12
12
  import {
13
13
  View,
14
14
  Text,
15
- TextInput,
16
- Pressable,
17
15
  ScrollView,
18
16
  KeyboardAvoidingView,
19
17
  Platform,
20
- ActivityIndicator,
21
18
  } from "react-native";
22
19
 
23
20
  import { Container } from "@/components/container";
@@ -32,7 +29,7 @@ function MessageContent({
32
29
  const [visibleText] = useSmoothText(text, {
33
30
  startStreaming: isStreaming,
34
31
  });
35
- return <Text className="text-foreground leading-relaxed">{visibleText}</Text>;
32
+ return <Text className="text-foreground text-sm leading-relaxed">{visibleText}</Text>;
36
33
  }
37
34
 
38
35
  export default function AIScreen() {
@@ -41,7 +38,6 @@ export default function AIScreen() {
41
38
  const [isLoading, setIsLoading] = useState(false);
42
39
  const scrollViewRef = useRef<ScrollView>(null);
43
40
  const mutedColor = useThemeColor("muted");
44
- const accentColor = useThemeColor("accent");
45
41
  const foregroundColor = useThemeColor("foreground");
46
42
 
47
43
  const createThread = useMutation(api.chat.createNewThread);
@@ -89,86 +85,80 @@ export default function AIScreen() {
89
85
  className="flex-1"
90
86
  behavior={Platform.OS === "ios" ? "padding" : "height"}
91
87
  >
92
- <View className="flex-1 px-4 py-6">
93
- <View className="mb-6">
94
- <Text className="text-foreground text-2xl font-bold mb-2">
95
- AI Chat
96
- </Text>
97
- <Text className="text-muted">Chat with our AI assistant</Text>
88
+ <View className="flex-1 px-4 py-4">
89
+ <View className="py-4 mb-4">
90
+ <Text className="text-2xl font-semibold text-foreground tracking-tight">AI Chat</Text>
91
+ <Text className="text-muted text-sm mt-1">Chat with our AI assistant</Text>
98
92
  </View>
93
+
99
94
  <ScrollView
100
95
  ref={scrollViewRef}
101
96
  className="flex-1 mb-4"
102
97
  showsVerticalScrollIndicator={false}
103
98
  >
104
99
  {!messages || messages.length === 0 ? (
105
- <View className="flex-1 justify-center items-center">
106
- <Text className="text-center text-muted text-lg">
107
- Ask me anything to get started!
108
- </Text>
100
+ <View className="flex-1 justify-center items-center py-10">
101
+ <Ionicons name="chatbubble-ellipses-outline" size={32} color={mutedColor} />
102
+ <Text className="text-muted text-sm mt-3">Ask me anything to get started</Text>
109
103
  </View>
110
104
  ) : (
111
- <View className="gap-3">
105
+ <View className="gap-2">
112
106
  {messages.map((message: UIMessage) => (
113
- <Card
107
+ <Surface
114
108
  key={message.key}
115
- variant="secondary"
116
- className={`p-3 ${message.role === "user" ? "ml-8 bg-accent/10" : "mr-8"}`}
109
+ variant={message.role === "user" ? "tertiary" : "secondary"}
110
+ className={`p-3 rounded-lg ${message.role === "user" ? "ml-10" : "mr-10"}`}
117
111
  >
118
- <Text className="text-sm font-semibold mb-1 text-foreground">
119
- {message.role === "user" ? "You" : "AI Assistant"}
112
+ <Text className="text-xs font-medium mb-1 text-muted">
113
+ {message.role === "user" ? "You" : "AI"}
120
114
  </Text>
121
115
  <MessageContent
122
116
  text={message.text ?? ""}
123
117
  isStreaming={message.status === "streaming"}
124
118
  />
125
- </Card>
119
+ </Surface>
126
120
  ))}
127
121
  {isLoading && !hasStreamingMessage && (
128
- <Card variant="secondary" className="p-3 mr-8">
129
- <Text className="text-sm font-semibold mb-1 text-foreground">
130
- AI Assistant
131
- </Text>
122
+ <Surface variant="secondary" className="p-3 mr-10 rounded-lg">
123
+ <Text className="text-xs font-medium mb-1 text-muted">AI</Text>
132
124
  <View className="flex-row items-center gap-2">
133
- <ActivityIndicator size="small" color={accentColor} />
134
- <Text className="text-muted">Thinking...</Text>
125
+ <Spinner size="sm" />
126
+ <Text className="text-muted text-sm">Thinking...</Text>
135
127
  </View>
136
- </Card>
128
+ </Surface>
137
129
  )}
138
130
  </View>
139
131
  )}
140
132
  </ScrollView>
141
- <View className="border-t border-divider pt-4">
142
- <View className="flex-row items-end gap-2">
143
- <TextInput
144
- value={input}
145
- onChangeText={setInput}
146
- placeholder="Type your message..."
147
- placeholderTextColor={mutedColor}
148
- className="flex-1 border border-divider rounded-lg px-3 py-2 text-foreground bg-surface min-h-10 max-h-30"
149
- onSubmitEditing={(e) => {
150
- e.preventDefault();
151
- onSubmit();
152
- }}
153
- editable={!isLoading}
154
- autoFocus={true}
155
- />
156
- <Pressable
157
- onPress={onSubmit}
158
- disabled={!input.trim() || isLoading}
159
- className={`p-2 rounded-lg active:opacity-70 ${
160
- input.trim() && !isLoading ? "bg-accent" : "bg-surface"
161
- }`}
162
- >
163
- <Ionicons
164
- name="send"
165
- size={20}
166
- color={
167
- input.trim() && !isLoading ? foregroundColor : mutedColor
168
- }
133
+
134
+ <Divider className="mb-3" />
135
+
136
+ <View className="flex-row items-center gap-2">
137
+ <View className="flex-1">
138
+ <TextField>
139
+ <TextField.Input
140
+ value={input}
141
+ onChangeText={setInput}
142
+ placeholder="Type a message..."
143
+ onSubmitEditing={onSubmit}
144
+ editable={!isLoading}
145
+ autoFocus
169
146
  />
170
- </Pressable>
147
+ </TextField>
171
148
  </View>
149
+ <Button
150
+ isIconOnly
151
+ variant={input.trim() && !isLoading ? "primary" : "secondary"}
152
+ onPress={onSubmit}
153
+ isDisabled={!input.trim() || isLoading}
154
+ size="sm"
155
+ >
156
+ <Ionicons
157
+ name="arrow-up"
158
+ size={18}
159
+ color={input.trim() && !isLoading ? foregroundColor : mutedColor}
160
+ />
161
+ </Button>
172
162
  </View>
173
163
  </View>
174
164
  </KeyboardAvoidingView>
@@ -180,8 +170,6 @@ import { useRef, useEffect, useState } from "react";
180
170
  import {
181
171
  View,
182
172
  Text,
183
- TextInput,
184
- Pressable,
185
173
  ScrollView,
186
174
  KeyboardAvoidingView,
187
175
  Platform,
@@ -191,7 +179,7 @@ import { DefaultChatTransport } from "ai";
191
179
  import { fetch as expoFetch } from "expo/fetch";
192
180
  import { Ionicons } from "@expo/vector-icons";
193
181
  import { Container } from "@/components/container";
194
- import { Card, useThemeColor } from "heroui-native";
182
+ import { Button, Divider, ErrorView, Spinner, Surface, TextField, useThemeColor } from "heroui-native";
195
183
  import { env } from "@{{projectName}}/env/native";
196
184
 
197
185
  const generateAPIUrl = (relativePath: string) => {
@@ -215,10 +203,8 @@ export default function AIScreen() {
215
203
  onError: (error) => console.error(error, "AI Chat Error"),
216
204
  });
217
205
  const scrollViewRef = useRef<ScrollView>(null);
218
- const mutedColor = useThemeColor("muted");
219
- const accentColor = useThemeColor("accent");
220
206
  const foregroundColor = useThemeColor("foreground");
221
- const dangerColor = useThemeColor("danger");
207
+ const mutedColor = useThemeColor("muted");
222
208
 
223
209
  useEffect(() => {
224
210
  scrollViewRef.current?.scrollToEnd({ animated: true });
@@ -236,14 +222,16 @@ export default function AIScreen() {
236
222
  return (
237
223
  <Container>
238
224
  <View className="flex-1 justify-center items-center px-4">
239
- <Card variant="flat" className="p-4 bg-danger/10">
240
- <Text className="text-danger text-center text-lg mb-2 font-semibold">
241
- Error: {error.message}
242
- </Text>
243
- <Text className="text-muted text-center">
244
- Please check your connection and try again.
245
- </Text>
246
- </Card>
225
+ <Surface variant="secondary" className="p-4 rounded-lg">
226
+ <ErrorView isInvalid>
227
+ <Text className="text-danger text-center font-medium mb-1">
228
+ {error.message}
229
+ </Text>
230
+ <Text className="text-muted text-center text-xs">
231
+ Please check your connection and try again.
232
+ </Text>
233
+ </ErrorView>
234
+ </Surface>
247
235
  </View>
248
236
  </Container>
249
237
  );
@@ -255,93 +243,85 @@ export default function AIScreen() {
255
243
  className="flex-1"
256
244
  behavior={Platform.OS === "ios" ? "padding" : "height"}
257
245
  >
258
- <View className="flex-1 px-4 py-6">
259
- <View className="mb-6">
260
- <Text className="text-foreground text-2xl font-bold mb-2">
261
- AI Chat
262
- </Text>
263
- <Text className="text-muted">
264
- Chat with our AI assistant
265
- </Text>
246
+ <View className="flex-1 px-4 py-4">
247
+ <View className="py-4 mb-4">
248
+ <Text className="text-2xl font-semibold text-foreground tracking-tight">AI Chat</Text>
249
+ <Text className="text-muted text-sm mt-1">Chat with our AI assistant</Text>
266
250
  </View>
251
+
267
252
  <ScrollView
268
253
  ref={scrollViewRef}
269
254
  className="flex-1 mb-4"
270
255
  showsVerticalScrollIndicator={false}
271
256
  >
272
257
  {messages.length === 0 ? (
273
- <View className="flex-1 justify-center items-center">
274
- <Text className="text-center text-muted text-lg">
275
- Ask me anything to get started!
276
- </Text>
258
+ <View className="flex-1 justify-center items-center py-10">
259
+ <Ionicons name="chatbubble-ellipses-outline" size={32} color={mutedColor} />
260
+ <Text className="text-muted text-sm mt-3">Ask me anything to get started</Text>
277
261
  </View>
278
262
  ) : (
279
- <View className="gap-3">
263
+ <View className="gap-2">
280
264
  {messages.map((message) => (
281
- <Card
265
+ <Surface
282
266
  key={message.id}
283
- variant={message.role === "user" ? "flat" : "secondary"}
284
- className={`p-3 ${
285
- message.role === "user"
286
- ? "ml-8 bg-accent/10"
287
- : "mr-8"
288
- }`}
267
+ variant={message.role === "user" ? "tertiary" : "secondary"}
268
+ className={`p-3 rounded-lg ${message.role === "user" ? "ml-10" : "mr-10"}`}
289
269
  >
290
- <Text className="text-sm font-semibold mb-1 text-foreground">
291
- {message.role === "user" ? "You" : "AI Assistant"}
270
+ <Text className="text-xs font-medium mb-1 text-muted">
271
+ {message.role === "user" ? "You" : "AI"}
292
272
  </Text>
293
273
  <View className="gap-1">
294
274
  {message.parts.map((part, i) =>
295
275
  part.type === "text" ? (
296
276
  <Text
297
277
  key={`${message.id}-${i}`}
298
- className="text-foreground leading-relaxed"
278
+ className="text-foreground text-sm leading-relaxed"
299
279
  >
300
280
  {part.text}
301
281
  </Text>
302
282
  ) : (
303
283
  <Text
304
284
  key={`${message.id}-${i}`}
305
- className="text-foreground leading-relaxed"
285
+ className="text-foreground text-sm leading-relaxed"
306
286
  >
307
287
  {JSON.stringify(part)}
308
288
  </Text>
309
289
  )
310
290
  )}
311
291
  </View>
312
- </Card>
292
+ </Surface>
313
293
  ))}
314
294
  </View>
315
295
  )}
316
296
  </ScrollView>
317
- <View className="border-t border-divider pt-4">
318
- <View className="flex-row items-end gap-2">
319
- <TextInput
320
- value={input}
321
- onChangeText={setInput}
322
- placeholder="Type your message..."
323
- placeholderTextColor={mutedColor}
324
- className="flex-1 border border-divider rounded-lg px-3 py-2 text-foreground bg-surface min-h-[40px] max-h-[120px]"
325
- onSubmitEditing={(e) => {
326
- e.preventDefault();
327
- onSubmit();
328
- }}
329
- autoFocus={true}
330
- />
331
- <Pressable
332
- onPress={onSubmit}
333
- disabled={!input.trim()}
334
- className={`p-2 rounded-lg active:opacity-70 ${
335
- input.trim() ? "bg-accent" : "bg-surface"
336
- }`}
337
- >
338
- <Ionicons
339
- name="send"
340
- size={20}
341
- color={input.trim() ? foregroundColor : mutedColor}
297
+
298
+ <Divider className="mb-3" />
299
+
300
+ <View className="flex-row items-center gap-2">
301
+ <View className="flex-1">
302
+ <TextField>
303
+ <TextField.Input
304
+ value={input}
305
+ onChangeText={setInput}
306
+ placeholder="Type a message..."
307
+ onSubmitEditing={onSubmit}
308
+ autoFocus
342
309
  />
343
- </Pressable>
310
+ </TextField>
344
311
  </View>
312
+ <Button
313
+ isIconOnly
314
+ variant={input.trim() ? "primary" : "secondary"}
315
+ onPress={onSubmit}
316
+ isDisabled={!input.trim()}
317
+ size="sm"
318
+ >
319
+ <Ionicons
320
+ name="arrow-up"
321
+ size={18}
322
+ color={input.trim() ? foregroundColor : mutedColor}
323
+ />
324
+ </Button>
345
325
  </View>
346
326
  </View>
347
327
  </KeyboardAvoidingView>
@@ -23,32 +23,32 @@ async function handleSubmit(e: Event) {
23
23
  e.preventDefault()
24
24
  const userInput = input.value
25
25
  input.value = ''
26
-
26
+
27
27
  if (!userInput.trim()) return
28
-
28
+
29
29
  chat.sendMessage({ text: userInput })
30
30
  }
31
31
  </script>
32
32
 
33
33
  <template>
34
- <div class="grid grid-rows-[1fr_auto] w-full h-full mx-auto p-4">
35
- <UChatMessages :messages="chat.messages" :status="chat.status">
36
- <template #content="{ message }">
37
- <div class="whitespace-pre-wrap">\{{ getTextFromMessage(message) }}</div>
38
- </template>
39
- </UChatMessages>
40
- <UChatPrompt
41
- v-model="input"
42
- :error="chat.error"
43
- @submit="handleSubmit"
44
- placeholder="Type your message..."
45
- class="w-full"
46
- >
47
- <UChatPromptSubmit
48
- :status="chat.status"
49
- @stop="chat.stop"
50
- @reload="chat.regenerate"
51
- />
52
- </UChatPrompt>
53
- </div>
34
+ <UContainer class="h-full flex flex-col overflow-hidden py-4">
35
+ <div class="flex-1 min-h-0 overflow-y-auto">
36
+ <UChatMessages :messages="chat.messages" :status="chat.status">
37
+ <template #content="{ message }">
38
+ <div class="whitespace-pre-wrap">\{{ getTextFromMessage(message) }}</div>
39
+ </template>
40
+ </UChatMessages>
41
+ </div>
42
+
43
+ <div class="shrink-0 pt-4 border-t border-gray-200 dark:border-gray-800">
44
+ <UChatPrompt
45
+ v-model="input"
46
+ :error="chat.error"
47
+ @submit="handleSubmit"
48
+ placeholder="Type your message..."
49
+ >
50
+ <UChatPromptSubmit :status="chat.status" @stop="() => chat.stop()" @reload="() => chat.regenerate()" />
51
+ </UChatPrompt>
52
+ </div>
53
+ </UContainer>
54
54
  </template>