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
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { r as createBtsCli } from "./src-D3EHRs6z.mjs";
2
+ import { r as createBtsCli } from "./src-DNjxspj9.mjs";
3
3
 
4
4
  //#region src/cli.ts
5
5
  createBtsCli().run();
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { a as router, i as docs, n as create, o as sponsors, r as createBtsCli, t as builder } from "./src-D3EHRs6z.mjs";
2
+ import { a as router, i as docs, n as create, o as sponsors, r as createBtsCli, t as builder } from "./src-DNjxspj9.mjs";
3
3
 
4
4
  export { builder, create, createBtsCli, docs, router, sponsors };
@@ -100,7 +100,7 @@ const dependencyVersionMap = {
100
100
  husky: "^9.1.7",
101
101
  "lint-staged": "^16.1.2",
102
102
  tsx: "^4.19.2",
103
- "@types/node": "^22.13.11",
103
+ "@types/node": "^22.13.14",
104
104
  "@types/bun": "^1.3.4",
105
105
  "@elysiajs/node": "^1.3.1",
106
106
  "@elysiajs/cors": "^1.3.3",
@@ -116,13 +116,14 @@ const dependencyVersionMap = {
116
116
  fastify: "^5.3.3",
117
117
  "@fastify/cors": "^11.0.1",
118
118
  turbo: "^2.6.3",
119
- ai: "^5.0.49",
120
- "@ai-sdk/google": "^2.0.51",
121
- "@ai-sdk/vue": "^2.0.49",
122
- "@ai-sdk/svelte": "^3.0.39",
123
- "@ai-sdk/react": "^2.0.39",
119
+ ai: "^6.0.3",
120
+ "@ai-sdk/google": "^3.0.1",
121
+ "@ai-sdk/vue": "^3.0.3",
122
+ "@ai-sdk/svelte": "^4.0.3",
123
+ "@ai-sdk/react": "^3.0.3",
124
+ "@ai-sdk/devtools": "^0.0.2",
124
125
  streamdown: "^1.6.10",
125
- shiki: "^3.12.2",
126
+ shiki: "^3.20.0",
126
127
  "@orpc/server": "^1.12.2",
127
128
  "@orpc/client": "^1.12.2",
128
129
  "@orpc/openapi": "^1.12.2",
@@ -292,8 +293,10 @@ function allowedApisForFrontends(frontends = []) {
292
293
  if (includesNuxt || includesSvelte || includesSolid) return ["orpc", "none"];
293
294
  return base;
294
295
  }
295
- function isExampleTodoAllowed(backend, database) {
296
- return !(backend !== "convex" && backend !== "none" && database === "none");
296
+ function isExampleTodoAllowed(backend, database, api) {
297
+ if (backend === "convex") return true;
298
+ if (database === "none" || api === "none") return false;
299
+ return true;
297
300
  }
298
301
  function isExampleAIAllowed(backend, frontends = []) {
299
302
  if (frontends.includes("solid")) return false;
@@ -343,10 +346,13 @@ function validatePaymentsCompatibility(payments, auth, _backend, frontends = [])
343
346
  if (web.length === 0 && frontends.length > 0) exitWithError("Polar payments requires a web frontend or no frontend. Please select a web frontend or choose a different payments provider.");
344
347
  }
345
348
  }
346
- function validateExamplesCompatibility(examples, backend, database, frontend) {
349
+ function validateExamplesCompatibility(examples, backend, database, frontend, api) {
347
350
  const examplesArr = examples ?? [];
348
351
  if (examplesArr.length === 0 || examplesArr.includes("none")) return;
349
- if (examplesArr.includes("todo") && backend !== "convex" && backend !== "none" && database === "none") exitWithError("The 'todo' example requires a database if a backend (other than Convex) is present. Cannot use --examples todo when database is 'none' and a backend is selected.");
352
+ if (examplesArr.includes("todo") && backend !== "convex") {
353
+ if (database === "none") exitWithError("The 'todo' example requires a database. Cannot use --examples todo when database is 'none'.");
354
+ if (api === "none") exitWithError("The 'todo' example requires an API layer (tRPC or oRPC). Cannot use --examples todo when api is 'none'.");
355
+ }
350
356
  if (examplesArr.includes("ai") && (frontend ?? []).includes("solid")) exitWithError("The 'ai' example is not compatible with the Solid frontend.");
351
357
  if (examplesArr.includes("ai") && backend === "convex") {
352
358
  const frontendArr = frontend ?? [];
@@ -578,6 +584,7 @@ async function getAuthChoice(auth, backend, frontend) {
578
584
  label: "Clerk",
579
585
  hint: "More than auth, Complete User Management"
580
586
  });
587
+ if (options.length === 0) return "none";
581
588
  options.push({
582
589
  value: "none",
583
590
  label: "None",
@@ -802,13 +809,9 @@ async function getDBSetupChoice(databaseType, dbSetup, _orm, backend, runtime) {
802
809
  async function getExamplesChoice(examples, database, frontends, backend, api) {
803
810
  if (examples !== void 0) return examples;
804
811
  if (backend === "none") return [];
805
- if (backend !== "convex") {
806
- if (api === "none") return [];
807
- if (database === "none") return [];
808
- }
809
812
  let response = [];
810
813
  const options = [];
811
- if (isExampleTodoAllowed(backend, database)) options.push({
814
+ if (isExampleTodoAllowed(backend, database, api)) options.push({
812
815
  value: "todo",
813
816
  label: "Todo App",
814
817
  hint: "A simple CRUD example app"
@@ -1758,7 +1761,7 @@ function validateFrontendConstraints(config, providedFlags) {
1758
1761
  }
1759
1762
  function validateApiConstraints(config, options) {
1760
1763
  if (config.api === "none") {
1761
- if (options.examples && !(options.examples.length === 1 && options.examples[0] === "none") && options.backend !== "convex" && options.backend !== "none") exitWithError("Cannot use '--examples' when '--api' is set to 'none'. Please remove the --examples flag or choose an API type.");
1764
+ if (options.examples?.includes("todo") && options.backend !== "convex" && options.backend !== "none") exitWithError("Cannot use '--examples todo' when '--api' is set to 'none'. The todo example requires an API layer. Please remove 'todo' from --examples or choose an API type.");
1762
1765
  }
1763
1766
  }
1764
1767
  function validateFullConfig(config, providedFlags, options) {
@@ -1779,7 +1782,7 @@ function validateFullConfig(config, providedFlags, options) {
1779
1782
  validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth);
1780
1783
  config.addons = [...new Set(config.addons)];
1781
1784
  }
1782
- validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? []);
1785
+ validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? [], config.api);
1783
1786
  validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend ?? []);
1784
1787
  }
1785
1788
  function validateConfigForProgrammaticUse(config) {
@@ -1789,7 +1792,7 @@ function validateConfigForProgrammaticUse(config) {
1789
1792
  validateApiFrontendCompatibility(config.api, config.frontend);
1790
1793
  validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend);
1791
1794
  if (config.addons && config.addons.length > 0) validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth);
1792
- validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? []);
1795
+ validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? [], config.api);
1793
1796
  } catch (error) {
1794
1797
  if (error instanceof Error) throw error;
1795
1798
  throw new Error(String(error));
@@ -4007,11 +4010,19 @@ async function setupAIDependencies(config) {
4007
4010
  projectDir: convexBackendDir
4008
4011
  });
4009
4012
  else if (backend === "self" && webClientDirExists) await addPackageDependency({
4010
- dependencies: ["ai", "@ai-sdk/google"],
4013
+ dependencies: [
4014
+ "ai",
4015
+ "@ai-sdk/google",
4016
+ "@ai-sdk/devtools"
4017
+ ],
4011
4018
  projectDir: webClientDir
4012
4019
  });
4013
4020
  else if (serverDirExists && backend !== "none") await addPackageDependency({
4014
- dependencies: ["ai", "@ai-sdk/google"],
4021
+ dependencies: [
4022
+ "ai",
4023
+ "@ai-sdk/google",
4024
+ "@ai-sdk/devtools"
4025
+ ],
4015
4026
  projectDir: serverDir
4016
4027
  });
4017
4028
  if (webClientDirExists) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "3.12.5",
3
+ "version": "3.12.7",
4
4
  "description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
5
5
  "keywords": [
6
6
  "better-auth",
@@ -67,7 +67,7 @@
67
67
  "prepublishOnly": "npm run build"
68
68
  },
69
69
  "dependencies": {
70
- "@better-t-stack/types": "^3.12.5",
70
+ "@better-t-stack/types": "^3.12.7",
71
71
  "@clack/prompts": "^1.0.0-alpha.8",
72
72
  "@orpc/server": "^1.13.0",
73
73
  "consola": "^3.4.2",
@@ -11,7 +11,7 @@ import { appRouter } from "@{{projectName}}/api/routers/index";
11
11
  import { createContext } from "@{{projectName}}/api/context";
12
12
  {{else if (includes frontend "tanstack-start")}}
13
13
  import type { RouterClient } from "@orpc/server";
14
- import { appRouter } from "@{{projectName}}/api/routers/index";
14
+ import type { AppRouter } from "@{{projectName}}/api/routers/index";
15
15
  import { env } from "@{{projectName}}/env/web";
16
16
  {{else}}
17
17
  import type { AppRouterClient } from "@{{projectName}}/api/routers/index";
@@ -73,10 +73,10 @@ const link = new RPCLink({
73
73
  });
74
74
 
75
75
  const getORPCClient = () => {
76
- return createORPCClient(link) as RouterClient<typeof appRouter>;
76
+ return createORPCClient(link) as RouterClient<AppRouter>;
77
77
  };
78
78
 
79
- export const client: RouterClient<typeof appRouter> = getORPCClient();
79
+ export const client: RouterClient<AppRouter> = getORPCClient();
80
80
  {{else}}
81
81
  export const link = new RPCLink({
82
82
  {{#if (and (eq backend "self") (includes frontend "next"))}}
@@ -1,13 +1,7 @@
1
1
  import { authClient } from "@/lib/auth-client";
2
2
  import { useState } from "react";
3
- import {
4
- ActivityIndicator,
5
- Text,
6
- TextInput,
7
- Pressable,
8
- View,
9
- } from "react-native";
10
- import { Card, useThemeColor } from "heroui-native";
3
+ import { Text, View } from "react-native";
4
+ import { Button, ErrorView, Spinner, Surface, TextField } from "heroui-native";
11
5
 
12
6
  export function SignIn() {
13
7
  const [email, setEmail] = useState("");
@@ -15,11 +9,6 @@ export function SignIn() {
15
9
  const [isLoading, setIsLoading] = useState(false);
16
10
  const [error, setError] = useState<string | null>(null);
17
11
 
18
- const mutedColor = useThemeColor("muted");
19
- const accentColor = useThemeColor("accent");
20
- const foregroundColor = useThemeColor("foreground");
21
- const dangerColor = useThemeColor("danger");
22
-
23
12
  const handleLogin = async () => {
24
13
  setIsLoading(true);
25
14
  setError(null);
@@ -46,46 +35,39 @@ export function SignIn() {
46
35
  };
47
36
 
48
37
  return (
49
- <Card variant="secondary" className="mt-6 p-4">
50
- <Card.Title className="mb-4">Sign In</Card.Title>
38
+ <Surface variant="secondary" className="p-4 rounded-lg">
39
+ <Text className="text-foreground font-medium mb-4">Sign In</Text>
51
40
 
52
- {error && (
53
- <View className="mb-4 p-3 bg-danger/10 rounded-lg">
54
- <Text className="text-danger text-sm">{error}</Text>
55
- </View>
56
- )}
41
+ <ErrorView isInvalid={!!error} className="mb-3">
42
+ {error}
43
+ </ErrorView>
57
44
 
58
- <TextInput
59
- className="mb-3 py-3 px-4 rounded-lg bg-surface text-foreground border border-divider"
60
- placeholder="Email"
61
- value={email}
62
- onChangeText={setEmail}
63
- placeholderTextColor={mutedColor}
64
- keyboardType="email-address"
65
- autoCapitalize="none"
66
- />
45
+ <View className="gap-3">
46
+ <TextField>
47
+ <TextField.Label>Email</TextField.Label>
48
+ <TextField.Input
49
+ value={email}
50
+ onChangeText={setEmail}
51
+ placeholder="email@example.com"
52
+ keyboardType="email-address"
53
+ autoCapitalize="none"
54
+ />
55
+ </TextField>
67
56
 
68
- <TextInput
69
- className="mb-4 py-3 px-4 rounded-lg bg-surface text-foreground border border-divider"
70
- placeholder="Password"
71
- value={password}
72
- onChangeText={setPassword}
73
- placeholderTextColor={mutedColor}
74
- secureTextEntry
75
- />
57
+ <TextField>
58
+ <TextField.Label>Password</TextField.Label>
59
+ <TextField.Input
60
+ value={password}
61
+ onChangeText={setPassword}
62
+ placeholder="••••••••"
63
+ secureTextEntry
64
+ />
65
+ </TextField>
76
66
 
77
- <Pressable
78
- onPress={handleLogin}
79
- disabled={isLoading}
80
- className="bg-accent p-4 rounded-lg flex-row justify-center items-center active:opacity-70"
81
- >
82
- {isLoading ? (
83
- <ActivityIndicator size="small" color={foregroundColor} />
84
- ) : (
85
- <Text className="text-foreground font-medium">Sign In</Text>
86
- )}
87
- </Pressable>
88
- </Card>
67
+ <Button onPress={handleLogin} isDisabled={isLoading} className="mt-1">
68
+ {isLoading ? <Spinner size="sm" color="default" /> : <Button.Label>Sign In</Button.Label>}
69
+ </Button>
70
+ </View>
71
+ </Surface>
89
72
  );
90
73
  }
91
-
@@ -1,13 +1,7 @@
1
1
  import { authClient } from "@/lib/auth-client";
2
2
  import { useState } from "react";
3
- import {
4
- ActivityIndicator,
5
- Text,
6
- TextInput,
7
- Pressable,
8
- View,
9
- } from "react-native";
10
- import { Card, useThemeColor } from "heroui-native";
3
+ import { Text, View } from "react-native";
4
+ import { Button, ErrorView, Spinner, Surface, TextField } from "heroui-native";
11
5
 
12
6
  export function SignUp() {
13
7
  const [name, setName] = useState("");
@@ -16,11 +10,6 @@ export function SignUp() {
16
10
  const [isLoading, setIsLoading] = useState(false);
17
11
  const [error, setError] = useState<string | null>(null);
18
12
 
19
- const mutedColor = useThemeColor("muted");
20
- const accentColor = useThemeColor("accent");
21
- const foregroundColor = useThemeColor("foreground");
22
- const dangerColor = useThemeColor("danger");
23
-
24
13
  const handleSignUp = async () => {
25
14
  setIsLoading(true);
26
15
  setError(null);
@@ -49,54 +38,48 @@ export function SignUp() {
49
38
  };
50
39
 
51
40
  return (
52
- <Card variant="secondary" className="mt-6 p-4">
53
- <Card.Title className="mb-4">Create Account</Card.Title>
41
+ <Surface variant="secondary" className="p-4 rounded-lg">
42
+ <Text className="text-foreground font-medium mb-4">Create Account</Text>
54
43
 
55
- {error && (
56
- <View className="mb-4 p-3 bg-danger/10 rounded-lg">
57
- <Text className="text-danger text-sm">{error}</Text>
58
- </View>
59
- )}
44
+ <ErrorView isInvalid={!!error} className="mb-3">
45
+ {error}
46
+ </ErrorView>
60
47
 
61
- <TextInput
62
- className="mb-3 py-3 px-4 rounded-lg bg-surface text-foreground border border-divider"
63
- placeholder="Name"
64
- value={name}
65
- onChangeText={setName}
66
- placeholderTextColor={mutedColor}
67
- />
48
+ <View className="gap-3">
49
+ <TextField>
50
+ <TextField.Label>Name</TextField.Label>
51
+ <TextField.Input value={name} onChangeText={setName} placeholder="John Doe" />
52
+ </TextField>
68
53
 
69
- <TextInput
70
- className="mb-3 py-3 px-4 rounded-lg bg-surface text-foreground border border-divider"
71
- placeholder="Email"
72
- value={email}
73
- onChangeText={setEmail}
74
- placeholderTextColor={mutedColor}
75
- keyboardType="email-address"
76
- autoCapitalize="none"
77
- />
54
+ <TextField>
55
+ <TextField.Label>Email</TextField.Label>
56
+ <TextField.Input
57
+ value={email}
58
+ onChangeText={setEmail}
59
+ placeholder="email@example.com"
60
+ keyboardType="email-address"
61
+ autoCapitalize="none"
62
+ />
63
+ </TextField>
78
64
 
79
- <TextInput
80
- className="mb-4 py-3 px-4 rounded-lg bg-surface text-foreground border border-divider"
81
- placeholder="Password"
82
- value={password}
83
- onChangeText={setPassword}
84
- placeholderTextColor={mutedColor}
85
- secureTextEntry
86
- />
65
+ <TextField>
66
+ <TextField.Label>Password</TextField.Label>
67
+ <TextField.Input
68
+ value={password}
69
+ onChangeText={setPassword}
70
+ placeholder="••••••••"
71
+ secureTextEntry
72
+ />
73
+ </TextField>
87
74
 
88
- <Pressable
89
- onPress={handleSignUp}
90
- disabled={isLoading}
91
- className="bg-accent p-4 rounded-lg flex-row justify-center items-center active:opacity-70"
92
- >
93
- {isLoading ? (
94
- <ActivityIndicator size="small" color={foregroundColor} />
95
- ) : (
96
- <Text className="text-foreground font-medium">Sign Up</Text>
97
- )}
98
- </Pressable>
99
- </Card>
75
+ <Button onPress={handleSignUp} isDisabled={isLoading} className="mt-1">
76
+ {isLoading ? (
77
+ <Spinner size="sm" color="default" />
78
+ ) : (
79
+ <Button.Label>Create Account</Button.Label>
80
+ )}
81
+ </Button>
82
+ </View>
83
+ </Surface>
100
84
  );
101
85
  }
102
-
@@ -6,14 +6,8 @@ import { queryClient } from "@/utils/trpc";
6
6
  import { queryClient } from "@/utils/orpc";
7
7
  {{/if}}
8
8
  import { useState } from "react";
9
- import {
10
- ActivityIndicator,
11
- Text,
12
- TextInput,
13
- Pressable,
14
- View,
15
- } from "react-native";
16
- import { Card, useThemeColor } from "heroui-native";
9
+ import { Text, View } from "react-native";
10
+ import { Button, ErrorView, Spinner, Surface, TextField } from "heroui-native";
17
11
 
18
12
  function SignIn() {
19
13
  const [email, setEmail] = useState("");
@@ -21,11 +15,6 @@ const [password, setPassword] = useState("");
21
15
  const [isLoading, setIsLoading] = useState(false);
22
16
  const [error, setError] = useState<string | null>(null);
23
17
 
24
- const mutedColor = useThemeColor("muted");
25
- const accentColor = useThemeColor("accent");
26
- const foregroundColor = useThemeColor("foreground");
27
- const dangerColor = useThemeColor("danger");
28
-
29
18
  async function handleLogin() {
30
19
  setIsLoading(true);
31
20
  setError(null);
@@ -58,32 +47,40 @@ const [error, setError] = useState<string | null>(null);
58
47
  }
59
48
 
60
49
  return (
61
- <Card variant="secondary" className="mt-6 p-4">
62
- <Card.Title className="mb-4">Sign In</Card.Title>
50
+ <Surface variant="secondary" className="p-4 rounded-lg">
51
+ <Text className="text-foreground font-medium mb-4">Sign In</Text>
63
52
 
64
- {error ? (
65
- <View className="mb-4 p-3 bg-danger/10 rounded-lg">
66
- <Text className="text-danger text-sm">{error}</Text>
67
- </View>
68
- ) : null}
53
+ <ErrorView isInvalid={!!error} className="mb-3">
54
+ {error}
55
+ </ErrorView>
69
56
 
70
- <TextInput className="mb-3 py-3 px-4 rounded-lg bg-surface text-foreground border border-divider"
71
- placeholder="Email" value={email} onChangeText={setEmail} placeholderTextColor={mutedColor}
72
- keyboardType="email-address" autoCapitalize="none" />
57
+ <View className="gap-3">
58
+ <TextField>
59
+ <TextField.Label>Email</TextField.Label>
60
+ <TextField.Input
61
+ value={email}
62
+ onChangeText={setEmail}
63
+ placeholder="email@example.com"
64
+ keyboardType="email-address"
65
+ autoCapitalize="none"
66
+ />
67
+ </TextField>
73
68
 
74
- <TextInput className="mb-4 py-3 px-4 rounded-lg bg-surface text-foreground border border-divider"
75
- placeholder="Password" value={password} onChangeText={setPassword} placeholderTextColor={mutedColor}
76
- secureTextEntry />
69
+ <TextField>
70
+ <TextField.Label>Password</TextField.Label>
71
+ <TextField.Input
72
+ value={password}
73
+ onChangeText={setPassword}
74
+ placeholder="••••••••"
75
+ secureTextEntry
76
+ />
77
+ </TextField>
77
78
 
78
- <Pressable onPress={handleLogin} disabled={isLoading}
79
- className="bg-accent p-4 rounded-lg flex-row justify-center items-center active:opacity-70">
80
- {isLoading ? (
81
- <ActivityIndicator size="small" color={foregroundColor} />
82
- ) : (
83
- <Text className="text-foreground font-medium">Sign In</Text>
84
- )}
85
- </Pressable>
86
- </Card>
79
+ <Button onPress={handleLogin} isDisabled={isLoading} className="mt-1">
80
+ {isLoading ? <Spinner size="sm" color="default" /> : <Button.Label>Sign In</Button.Label>}
81
+ </Button>
82
+ </View>
83
+ </Surface>
87
84
  );
88
85
  }
89
86
 
@@ -6,14 +6,8 @@ import { queryClient } from "@/utils/trpc";
6
6
  import { queryClient } from "@/utils/orpc";
7
7
  {{/if}}
8
8
  import { useState } from "react";
9
- import {
10
- ActivityIndicator,
11
- Text,
12
- TextInput,
13
- Pressable,
14
- View,
15
- } from "react-native";
16
- import { Card, useThemeColor } from "heroui-native";
9
+ import { Text, View } from "react-native";
10
+ import { Button, ErrorView, Spinner, Surface, TextField } from "heroui-native";
17
11
 
18
12
  function signUpHandler({
19
13
  name,
@@ -24,6 +18,15 @@ setIsLoading,
24
18
  setName,
25
19
  setEmail,
26
20
  setPassword,
21
+ }: {
22
+ name: string;
23
+ email: string;
24
+ password: string;
25
+ setError: (error: string | null) => void;
26
+ setIsLoading: (loading: boolean) => void;
27
+ setName: (name: string) => void;
28
+ setEmail: (email: string) => void;
29
+ setPassword: (password: string) => void;
27
30
  }) {
28
31
  setIsLoading(true);
29
32
  setError(null);
@@ -64,11 +67,6 @@ const [password, setPassword] = useState("");
64
67
  const [isLoading, setIsLoading] = useState(false);
65
68
  const [error, setError] = useState<string | null>(null);
66
69
 
67
- const mutedColor = useThemeColor("muted");
68
- const accentColor = useThemeColor("accent");
69
- const foregroundColor = useThemeColor("foreground");
70
- const dangerColor = useThemeColor("danger");
71
-
72
70
  function handlePress() {
73
71
  signUpHandler({
74
72
  name,
@@ -83,34 +81,48 @@ const [error, setError] = useState<string | null>(null);
83
81
  }
84
82
 
85
83
  return (
86
- <Card variant="secondary" className="mt-6 p-4">
87
- <Card.Title className="mb-4">Create Account</Card.Title>
84
+ <Surface variant="secondary" className="p-4 rounded-lg">
85
+ <Text className="text-foreground font-medium mb-4">Create Account</Text>
88
86
 
89
- {error && (
90
- <View className="mb-4 p-3 bg-danger/10 rounded-lg">
91
- <Text className="text-danger text-sm">{error}</Text>
92
- </View>
93
- )}
87
+ <ErrorView isInvalid={!!error} className="mb-3">
88
+ {error}
89
+ </ErrorView>
94
90
 
95
- <TextInput className="mb-3 py-3 px-4 rounded-lg bg-surface text-foreground border border-divider" placeholder="Name"
96
- value={name} onChangeText={setName} placeholderTextColor={mutedColor} />
91
+ <View className="gap-3">
92
+ <TextField>
93
+ <TextField.Label>Name</TextField.Label>
94
+ <TextField.Input value={name} onChangeText={setName} placeholder="John Doe" />
95
+ </TextField>
97
96
 
98
- <TextInput className="mb-3 py-3 px-4 rounded-lg bg-surface text-foreground border border-divider"
99
- placeholder="Email" value={email} onChangeText={setEmail} placeholderTextColor={mutedColor}
100
- keyboardType="email-address" autoCapitalize="none" />
97
+ <TextField>
98
+ <TextField.Label>Email</TextField.Label>
99
+ <TextField.Input
100
+ value={email}
101
+ onChangeText={setEmail}
102
+ placeholder="email@example.com"
103
+ keyboardType="email-address"
104
+ autoCapitalize="none"
105
+ />
106
+ </TextField>
101
107
 
102
- <TextInput className="mb-4 py-3 px-4 rounded-lg bg-surface text-foreground border border-divider"
103
- placeholder="Password" value={password} onChangeText={setPassword} placeholderTextColor={mutedColor}
104
- secureTextEntry />
108
+ <TextField>
109
+ <TextField.Label>Password</TextField.Label>
110
+ <TextField.Input
111
+ value={password}
112
+ onChangeText={setPassword}
113
+ placeholder="••••••••"
114
+ secureTextEntry
115
+ />
116
+ </TextField>
105
117
 
106
- <Pressable onPress={handlePress} disabled={isLoading}
107
- className="bg-accent p-4 rounded-lg flex-row justify-center items-center active:opacity-70">
108
- {isLoading ? (
109
- <ActivityIndicator size="small" color={foregroundColor} />
110
- ) : (
111
- <Text className="text-foreground font-medium">Sign Up</Text>
112
- )}
113
- </Pressable>
114
- </Card>
118
+ <Button onPress={handlePress} isDisabled={isLoading} className="mt-1">
119
+ {isLoading ? (
120
+ <Spinner size="sm" color="default" />
121
+ ) : (
122
+ <Button.Label>Create Account</Button.Label>
123
+ )}
124
+ </Button>
125
+ </View>
126
+ </Surface>
115
127
  );
116
128
  }