create-better-t-stack 3.1.6 → 3.1.8-canary.10bdbacd

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 (28) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/index.js +1 -1
  3. package/dist/{src-DeOVz-ZI.js → src-Nl2APXCU.js} +75 -17
  4. package/package.json +1 -1
  5. package/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs +50 -33
  6. package/templates/auth/better-auth/convex/backend/convex/http.ts.hbs +1 -1
  7. package/templates/auth/better-auth/convex/native/base/lib/auth-client.ts.hbs +19 -0
  8. package/templates/auth/better-auth/convex/native/nativewind/components/sign-in.tsx.hbs +86 -0
  9. package/templates/auth/better-auth/convex/native/nativewind/components/sign-up.tsx.hbs +97 -0
  10. package/templates/auth/better-auth/convex/native/unistyles/components/sign-in.tsx.hbs +127 -0
  11. package/templates/auth/better-auth/convex/native/unistyles/components/sign-up.tsx.hbs +145 -0
  12. package/templates/auth/better-auth/native/{native-base → base}/lib/auth-client.ts.hbs +3 -2
  13. package/templates/auth/better-auth/web/react/next/src/app/dashboard/page.tsx.hbs +11 -0
  14. package/templates/auth/better-auth/web/react/tanstack-start/src/middleware/auth.ts.hbs +20 -1
  15. package/templates/frontend/native/nativewind/app/(drawer)/index.tsx.hbs +151 -100
  16. package/templates/frontend/native/nativewind/app/_layout.tsx.hbs +21 -0
  17. package/templates/frontend/native/unistyles/app/(drawer)/index.tsx.hbs +201 -91
  18. package/templates/frontend/native/unistyles/app/_layout.tsx.hbs +28 -0
  19. /package/templates/frontend/native/{native-base → base}/assets/images/android-icon-background.png +0 -0
  20. /package/templates/frontend/native/{native-base → base}/assets/images/android-icon-foreground.png +0 -0
  21. /package/templates/frontend/native/{native-base → base}/assets/images/android-icon-monochrome.png +0 -0
  22. /package/templates/frontend/native/{native-base → base}/assets/images/favicon.png +0 -0
  23. /package/templates/frontend/native/{native-base → base}/assets/images/icon.png +0 -0
  24. /package/templates/frontend/native/{native-base → base}/assets/images/partial-react-logo.png +0 -0
  25. /package/templates/frontend/native/{native-base → base}/assets/images/react-logo.png +0 -0
  26. /package/templates/frontend/native/{native-base → base}/assets/images/react-logo@2x.png +0 -0
  27. /package/templates/frontend/native/{native-base → base}/assets/images/react-logo@3x.png +0 -0
  28. /package/templates/frontend/native/{native-base → base}/assets/images/splash-icon.png +0 -0
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { createBtsCli } from "./src-DeOVz-ZI.js";
2
+ import { createBtsCli } from "./src-Nl2APXCU.js";
3
3
 
4
4
  //#region src/cli.ts
5
5
  createBtsCli().run();
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { builder, createBtsCli, docs, init, router, sponsors } from "./src-DeOVz-ZI.js";
2
+ import { builder, createBtsCli, docs, init, router, sponsors } from "./src-Nl2APXCU.js";
3
3
 
4
4
  export { builder, createBtsCli, docs, init, router, sponsors };
@@ -627,7 +627,9 @@ async function getAuthChoice(auth, hasDatabase, backend, frontend) {
627
627
  const supportedBetterAuthFrontends = frontend?.some((f) => [
628
628
  "tanstack-router",
629
629
  "tanstack-start",
630
- "next"
630
+ "next",
631
+ "native-nativewind",
632
+ "native-unistyles"
631
633
  ].includes(f));
632
634
  const hasClerkCompatibleFrontends = frontend?.some((f) => [
633
635
  "react-router",
@@ -1388,7 +1390,7 @@ const getLatestCLIVersion = () => {
1388
1390
  */
1389
1391
  function isTelemetryEnabled() {
1390
1392
  const BTS_TELEMETRY_DISABLED = process.env.BTS_TELEMETRY_DISABLED;
1391
- const BTS_TELEMETRY = "1";
1393
+ const BTS_TELEMETRY = "0";
1392
1394
  if (BTS_TELEMETRY_DISABLED !== void 0) return BTS_TELEMETRY_DISABLED !== "1";
1393
1395
  if (BTS_TELEMETRY !== void 0) return BTS_TELEMETRY === "1";
1394
1396
  return true;
@@ -1396,8 +1398,8 @@ function isTelemetryEnabled() {
1396
1398
 
1397
1399
  //#endregion
1398
1400
  //#region src/utils/analytics.ts
1399
- const POSTHOG_API_KEY = "phc_8ZUxEwwfKMajJLvxz1daGd931dYbQrwKNficBmsdIrs";
1400
- const POSTHOG_HOST = "https://us.i.posthog.com";
1401
+ const POSTHOG_API_KEY = "random";
1402
+ const POSTHOG_HOST = "random";
1401
1403
  function generateSessionId() {
1402
1404
  const rand = Math.random().toString(36).slice(2);
1403
1405
  return `cli_${Date.now().toString(36)}${rand}`;
@@ -1750,9 +1752,11 @@ function validateConvexConstraints(config, providedFlags) {
1750
1752
  const supportedFrontends = [
1751
1753
  "tanstack-router",
1752
1754
  "tanstack-start",
1753
- "next"
1755
+ "next",
1756
+ "native-nativewind",
1757
+ "native-unistyles"
1754
1758
  ];
1755
- if (!config.frontend?.some((f) => supportedFrontends.includes(f))) exitWithError("Better-Auth with Convex backend is only supported with TanStack Router, TanStack Start, or Next.js frontends. Please use '--auth clerk' or '--auth none'.");
1759
+ if (!config.frontend?.some((f) => supportedFrontends.includes(f))) exitWithError("Better-Auth with Convex backend requires a supported frontend (TanStack Router, TanStack Start, Next.js, or Native).");
1756
1760
  }
1757
1761
  }
1758
1762
  function validateBackendNoneConstraints(config, providedFlags) {
@@ -2798,7 +2802,7 @@ async function setupFrontendTemplates(projectDir, context) {
2798
2802
  if (hasNativeWind || hasUnistyles) {
2799
2803
  const nativeAppDir = path.join(projectDir, "apps/native");
2800
2804
  await fs.ensureDir(nativeAppDir);
2801
- const nativeBaseCommonDir = path.join(PKG_ROOT, "templates/frontend/native/native-base");
2805
+ const nativeBaseCommonDir = path.join(PKG_ROOT, "templates/frontend/native/base");
2802
2806
  if (await fs.pathExists(nativeBaseCommonDir)) await processAndCopyFiles("**/*", nativeBaseCommonDir, nativeAppDir, context);
2803
2807
  let nativeFrameworkPath = "";
2804
2808
  if (hasNativeWind) nativeFrameworkPath = "nativewind";
@@ -2934,6 +2938,17 @@ async function setupAuthTemplate(projectDir, context) {
2934
2938
  if (await fs.pathExists(convexBetterAuthWebSrc)) await processAndCopyFiles("**/*", convexBetterAuthWebSrc, webAppDir, context);
2935
2939
  }
2936
2940
  }
2941
+ if (nativeAppDirExists) {
2942
+ const convexBetterAuthNativeBaseSrc = path.join(PKG_ROOT, "templates/auth/better-auth/convex/native/base");
2943
+ if (await fs.pathExists(convexBetterAuthNativeBaseSrc)) await processAndCopyFiles("**/*", convexBetterAuthNativeBaseSrc, nativeAppDir, context);
2944
+ let nativeFrameworkPath = "";
2945
+ if (hasNativeWind) nativeFrameworkPath = "nativewind";
2946
+ else if (hasUnistyles) nativeFrameworkPath = "unistyles";
2947
+ if (nativeFrameworkPath) {
2948
+ const convexBetterAuthNativeFrameworkSrc = path.join(PKG_ROOT, `templates/auth/better-auth/convex/native/${nativeFrameworkPath}`);
2949
+ if (await fs.pathExists(convexBetterAuthNativeFrameworkSrc)) await processAndCopyFiles("**/*", convexBetterAuthNativeFrameworkSrc, nativeAppDir, context);
2950
+ }
2951
+ }
2937
2952
  return;
2938
2953
  }
2939
2954
  if ((serverAppDirExists || context.backend === "self") && context.backend !== "convex") {
@@ -2983,7 +2998,7 @@ async function setupAuthTemplate(projectDir, context) {
2983
2998
  }
2984
2999
  }
2985
3000
  if (hasNative && nativeAppDirExists) {
2986
- const authNativeBaseSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/native/native-base`);
3001
+ const authNativeBaseSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/native/base`);
2987
3002
  if (await fs.pathExists(authNativeBaseSrc)) await processAndCopyFiles("**/*", authNativeBaseSrc, nativeAppDir, context);
2988
3003
  let nativeFrameworkAuthPath = "";
2989
3004
  if (hasNativeWind) nativeFrameworkAuthPath = "nativewind";
@@ -4467,11 +4482,20 @@ async function setupAuth(config) {
4467
4482
  }
4468
4483
  if (auth === "better-auth") {
4469
4484
  const convexBackendDir = path.join(projectDir, "packages/backend");
4470
- if (await fs.pathExists(convexBackendDir)) await addPackageDependency({
4471
- dependencies: ["better-auth", "@convex-dev/better-auth"],
4472
- customDependencies: { "better-auth": "1.3.27" },
4473
- projectDir: convexBackendDir
4474
- });
4485
+ const convexBackendDirExists = await fs.pathExists(convexBackendDir);
4486
+ const hasNativeForBA = frontend.includes("native-nativewind") || frontend.includes("native-unistyles");
4487
+ if (convexBackendDirExists) {
4488
+ await addPackageDependency({
4489
+ dependencies: ["better-auth", "@convex-dev/better-auth"],
4490
+ customDependencies: { "better-auth": "1.3.27" },
4491
+ projectDir: convexBackendDir
4492
+ });
4493
+ if (hasNativeForBA) await addPackageDependency({
4494
+ dependencies: ["@better-auth/expo"],
4495
+ customDependencies: { "@better-auth/expo": "1.3.27" },
4496
+ projectDir: convexBackendDir
4497
+ });
4498
+ }
4475
4499
  if (clientDirExists) {
4476
4500
  const hasNextJs = frontend.includes("next");
4477
4501
  const hasTanStackStart = frontend.includes("tanstack-start");
@@ -4492,6 +4516,20 @@ async function setupAuth(config) {
4492
4516
  projectDir: clientDir
4493
4517
  });
4494
4518
  }
4519
+ const hasNativeWind$1 = frontend.includes("native-nativewind");
4520
+ const hasUnistyles$1 = frontend.includes("native-unistyles");
4521
+ if (nativeDirExists && (hasNativeWind$1 || hasUnistyles$1)) await addPackageDependency({
4522
+ dependencies: [
4523
+ "better-auth",
4524
+ "@better-auth/expo",
4525
+ "@convex-dev/better-auth"
4526
+ ],
4527
+ customDependencies: {
4528
+ "better-auth": "1.3.27",
4529
+ "@better-auth/expo": "1.3.27"
4530
+ },
4531
+ projectDir: nativeDir
4532
+ });
4495
4533
  }
4496
4534
  const hasNativeWind = frontend.includes("native-nativewind");
4497
4535
  const hasUnistyles = frontend.includes("native-unistyles");
@@ -4709,6 +4747,11 @@ async function setupEnvironmentVariables(config) {
4709
4747
  value: "",
4710
4748
  condition: true
4711
4749
  });
4750
+ if (backend === "convex" && auth === "better-auth") nativeVars.push({
4751
+ key: "EXPO_PUBLIC_CONVEX_SITE_URL",
4752
+ value: "https://<YOUR_CONVEX_URL>",
4753
+ condition: true
4754
+ });
4712
4755
  await addEnvVariablesToFile(path.join(nativeDir, ".env"), nativeVars);
4713
4756
  }
4714
4757
  }
@@ -4717,12 +4760,26 @@ async function setupEnvironmentVariables(config) {
4717
4760
  const convexBackendDir = path.join(projectDir, "packages/backend");
4718
4761
  if (await fs.pathExists(convexBackendDir)) {
4719
4762
  const envLocalPath = path.join(convexBackendDir, ".env.local");
4720
- if (!await fs.pathExists(envLocalPath) || !(await fs.readFile(envLocalPath, "utf8")).includes("npx convex env set")) await fs.appendFile(envLocalPath, `# Set Convex environment variables
4763
+ const hasNative = frontend.includes("native-nativewind") || frontend.includes("native-unistyles");
4764
+ if (!await fs.pathExists(envLocalPath) || !(await fs.readFile(envLocalPath, "utf8")).includes("npx convex env set")) {
4765
+ const convexCommands = hasNative ? `# Set Convex environment variables
4766
+ # npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
4767
+
4768
+ ` : `# Set Convex environment variables
4721
4769
  # npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
4722
4770
  # npx convex env set SITE_URL http://localhost:3001
4723
4771
 
4724
- `);
4725
- await addEnvVariablesToFile(envLocalPath, [{
4772
+ `;
4773
+ await fs.appendFile(envLocalPath, convexCommands);
4774
+ }
4775
+ const convexBackendVars = [];
4776
+ if (hasNative) convexBackendVars.push({
4777
+ key: "EXPO_PUBLIC_CONVEX_SITE_URL",
4778
+ value: "",
4779
+ condition: true,
4780
+ comment: "Same as CONVEX_URL but ends in .site"
4781
+ });
4782
+ else convexBackendVars.push({
4726
4783
  key: hasNextJs ? "NEXT_PUBLIC_CONVEX_SITE_URL" : "VITE_CONVEX_SITE_URL",
4727
4784
  value: "",
4728
4785
  condition: true,
@@ -4731,7 +4788,8 @@ async function setupEnvironmentVariables(config) {
4731
4788
  key: "SITE_URL",
4732
4789
  value: "http://localhost:3001",
4733
4790
  condition: true
4734
- }]);
4791
+ });
4792
+ await addEnvVariablesToFile(envLocalPath, convexBackendVars);
4735
4793
  }
4736
4794
  }
4737
4795
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "3.1.6",
3
+ "version": "3.1.8-canary.10bdbacd",
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",
@@ -1,5 +1,8 @@
1
1
  import { createClient, type GenericCtx } from "@convex-dev/better-auth";
2
- {{#if (or (includes frontend "tanstack-start") (includes frontend "next"))}}
2
+ {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
3
+ import { convex } from "@convex-dev/better-auth/plugins";
4
+ import { expo } from "@better-auth/expo";
5
+ {{else if (or (includes frontend "tanstack-start") (includes frontend "next"))}}
3
6
  import { convex } from "@convex-dev/better-auth/plugins";
4
7
  {{else}}
5
8
  import { convex, crossDomain } from "@convex-dev/better-auth/plugins";
@@ -8,42 +11,56 @@ import { components } from "./_generated/api";
8
11
  import { DataModel } from "./_generated/dataModel";
9
12
  import { query } from "./_generated/server";
10
13
  import { betterAuth } from "better-auth";
14
+ import { v } from "convex/values";
11
15
 
16
+ {{#unless (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
12
17
  const siteUrl = process.env.SITE_URL!;
18
+ {{else}}
19
+ const nativeAppUrl = process.env.NATIVE_APP_URL || "mybettertapp://";
20
+ {{/unless}}
13
21
 
14
22
  export const authComponent = createClient<DataModel>(components.betterAuth);
15
23
 
16
- export const createAuth = (
17
- ctx: GenericCtx<DataModel>,
18
- { optionsOnly } = { optionsOnly: false },
19
- ) => {
20
- return betterAuth({
21
- logger: {
22
- disabled: optionsOnly,
23
- },
24
- {{#if (or (includes frontend "tanstack-start") (includes frontend "next"))}}
25
- baseURL: siteUrl,
26
- trustedOrigins: [siteUrl],
27
- {{else}}
28
- trustedOrigins: [siteUrl],
29
- {{/if}}
30
- database: authComponent.adapter(ctx),
31
- emailAndPassword: {
32
- enabled: true,
33
- requireEmailVerification: false,
34
- },
35
- plugins: [
36
- {{#unless (or (includes frontend "tanstack-start") (includes frontend "next"))}}
37
- crossDomain({ siteUrl }),
38
- {{/unless}}
39
- convex(),
40
- ],
41
- });
42
- };
24
+ function createAuth(
25
+ ctx: GenericCtx<DataModel>,
26
+ { optionsOnly }: { optionsOnly?: boolean } = { optionsOnly: false }
27
+ ) {
28
+ return betterAuth({
29
+ logger: {
30
+ disabled: optionsOnly,
31
+ },
32
+ {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
33
+ trustedOrigins: [nativeAppUrl],
34
+ {{else if (or (includes frontend "tanstack-start") (includes frontend "next"))}}
35
+ baseURL: siteUrl,
36
+ trustedOrigins: [siteUrl],
37
+ {{else}}
38
+ trustedOrigins: [siteUrl],
39
+ {{/if}}
40
+ database: authComponent.adapter(ctx),
41
+ emailAndPassword: {
42
+ enabled: true,
43
+ requireEmailVerification: false,
44
+ },
45
+ plugins: [
46
+ {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
47
+ expo(),
48
+ {{/if}}
49
+ {{#unless (or (includes frontend "tanstack-start") (includes frontend "next") (includes frontend
50
+ "native-nativewind") (includes frontend "native-unistyles"))}}
51
+ crossDomain({ siteUrl }),
52
+ {{/unless}}
53
+ convex(),
54
+ ],
55
+ });
56
+ }
57
+
58
+ export { createAuth };
43
59
 
44
60
  export const getCurrentUser = query({
45
- args: {},
46
- handler: async (ctx) => {
47
- return authComponent.getAuthUser(ctx);
48
- },
49
- });
61
+ args: {},
62
+ returns: v.any(),
63
+ handler: async function(ctx, args) {
64
+ return authComponent.getAuthUser(ctx);
65
+ },
66
+ });
@@ -3,7 +3,7 @@ import { authComponent, createAuth } from "./auth";
3
3
 
4
4
  const http = httpRouter();
5
5
 
6
- {{#if (or (includes frontend "tanstack-start") (includes frontend "next"))}}
6
+ {{#if (or (includes frontend "tanstack-start") (includes frontend "next") (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
7
7
  authComponent.registerRoutes(http, createAuth);
8
8
  {{else}}
9
9
  authComponent.registerRoutes(http, createAuth, { cors: true });
@@ -0,0 +1,19 @@
1
+ import { createAuthClient } from "better-auth/react";
2
+ import { anonymousClient } from "better-auth/client/plugins";
3
+ import { convexClient } from "@convex-dev/better-auth/client/plugins";
4
+ import { expoClient } from "@better-auth/expo/client";
5
+ import Constants from "expo-constants";
6
+ import * as SecureStore from "expo-secure-store";
7
+
8
+ export const authClient = createAuthClient({
9
+ baseURL: process.env.EXPO_PUBLIC_CONVEX_SITE_URL,
10
+ plugins: [
11
+ anonymousClient(),
12
+ expoClient({
13
+ scheme: Constants.expoConfig?.scheme as string,
14
+ storagePrefix: Constants.expoConfig?.scheme as string,
15
+ storage: SecureStore,
16
+ }),
17
+ convexClient(),
18
+ ],
19
+ });
@@ -0,0 +1,86 @@
1
+ import { authClient } from "@/lib/auth-client";
2
+ import { useState } from "react";
3
+ import {
4
+ ActivityIndicator,
5
+ Text,
6
+ TextInput,
7
+ TouchableOpacity,
8
+ View,
9
+ } from "react-native";
10
+
11
+ export function SignIn() {
12
+ const [email, setEmail] = useState("");
13
+ const [password, setPassword] = useState("");
14
+ const [isLoading, setIsLoading] = useState(false);
15
+ const [error, setError] = useState<string | null>(null);
16
+
17
+ const handleLogin = async () => {
18
+ setIsLoading(true);
19
+ setError(null);
20
+
21
+ await authClient.signIn.email(
22
+ {
23
+ email,
24
+ password,
25
+ },
26
+ {
27
+ onError: (error) => {
28
+ setError(error.error?.message || "Failed to sign in");
29
+ setIsLoading(false);
30
+ },
31
+ onSuccess: () => {
32
+ setEmail("");
33
+ setPassword("");
34
+ },
35
+ onFinished: () => {
36
+ setIsLoading(false);
37
+ },
38
+ },
39
+ );
40
+ };
41
+
42
+ return (
43
+ <View className="mt-6 p-4 bg-card rounded-lg border border-border">
44
+ <Text className="text-lg font-semibold text-foreground mb-4">
45
+ Sign In
46
+ </Text>
47
+
48
+ {error && (
49
+ <View className="mb-4 p-3 bg-destructive/10 rounded-md">
50
+ <Text className="text-destructive text-sm">{error}</Text>
51
+ </View>
52
+ )}
53
+
54
+ <TextInput
55
+ className="mb-3 p-4 rounded-md bg-input text-foreground border border-input"
56
+ placeholder="Email"
57
+ value={email}
58
+ onChangeText={setEmail}
59
+ placeholderTextColor="#9CA3AF"
60
+ keyboardType="email-address"
61
+ autoCapitalize="none"
62
+ />
63
+
64
+ <TextInput
65
+ className="mb-4 p-4 rounded-md bg-input text-foreground border border-input"
66
+ placeholder="Password"
67
+ value={password}
68
+ onChangeText={setPassword}
69
+ placeholderTextColor="#9CA3AF"
70
+ secureTextEntry
71
+ />
72
+
73
+ <TouchableOpacity
74
+ onPress={handleLogin}
75
+ disabled={isLoading}
76
+ className="bg-primary p-4 rounded-md flex-row justify-center items-center"
77
+ >
78
+ {isLoading ? (
79
+ <ActivityIndicator size="small" color="#fff" />
80
+ ) : (
81
+ <Text className="text-primary-foreground font-medium">Sign In</Text>
82
+ )}
83
+ </TouchableOpacity>
84
+ </View>
85
+ );
86
+ }
@@ -0,0 +1,97 @@
1
+ import { authClient } from "@/lib/auth-client";
2
+ import { useState } from "react";
3
+ import {
4
+ ActivityIndicator,
5
+ Text,
6
+ TextInput,
7
+ TouchableOpacity,
8
+ View,
9
+ } from "react-native";
10
+
11
+ export function SignUp() {
12
+ const [name, setName] = useState("");
13
+ const [email, setEmail] = useState("");
14
+ const [password, setPassword] = useState("");
15
+ const [isLoading, setIsLoading] = useState(false);
16
+ const [error, setError] = useState<string | null>(null);
17
+
18
+ const handleSignUp = async () => {
19
+ setIsLoading(true);
20
+ setError(null);
21
+
22
+ await authClient.signUp.email(
23
+ {
24
+ name,
25
+ email,
26
+ password,
27
+ },
28
+ {
29
+ onError: (error) => {
30
+ setError(error.error?.message || "Failed to sign up");
31
+ setIsLoading(false);
32
+ },
33
+ onSuccess: () => {
34
+ setName("");
35
+ setEmail("");
36
+ setPassword("");
37
+ },
38
+ onFinished: () => {
39
+ setIsLoading(false);
40
+ },
41
+ },
42
+ );
43
+ };
44
+
45
+ return (
46
+ <View className="mt-6 p-4 bg-card rounded-lg border border-border">
47
+ <Text className="text-lg font-semibold text-foreground mb-4">
48
+ Create Account
49
+ </Text>
50
+
51
+ {error && (
52
+ <View className="mb-4 p-3 bg-destructive/10 rounded-md">
53
+ <Text className="text-destructive text-sm">{error}</Text>
54
+ </View>
55
+ )}
56
+
57
+ <TextInput
58
+ className="mb-3 p-4 rounded-md bg-input text-foreground border border-input"
59
+ placeholder="Name"
60
+ value={name}
61
+ onChangeText={setName}
62
+ placeholderTextColor="#9CA3AF"
63
+ />
64
+
65
+ <TextInput
66
+ className="mb-3 p-4 rounded-md bg-input text-foreground border border-input"
67
+ placeholder="Email"
68
+ value={email}
69
+ onChangeText={setEmail}
70
+ placeholderTextColor="#9CA3AF"
71
+ keyboardType="email-address"
72
+ autoCapitalize="none"
73
+ />
74
+
75
+ <TextInput
76
+ className="mb-4 p-4 rounded-md bg-input text-foreground border border-input"
77
+ placeholder="Password"
78
+ value={password}
79
+ onChangeText={setPassword}
80
+ placeholderTextColor="#9CA3AF"
81
+ secureTextEntry
82
+ />
83
+
84
+ <TouchableOpacity
85
+ onPress={handleSignUp}
86
+ disabled={isLoading}
87
+ className="bg-primary p-4 rounded-md flex-row justify-center items-center"
88
+ >
89
+ {isLoading ? (
90
+ <ActivityIndicator size="small" color="#fff" />
91
+ ) : (
92
+ <Text className="text-primary-foreground font-medium">Sign Up</Text>
93
+ )}
94
+ </TouchableOpacity>
95
+ </View>
96
+ );
97
+ }
@@ -0,0 +1,127 @@
1
+ import { authClient } from "@/lib/auth-client";
2
+ import { useState } from "react";
3
+ import {
4
+ ActivityIndicator,
5
+ Text,
6
+ TextInput,
7
+ TouchableOpacity,
8
+ View,
9
+ } from "react-native";
10
+ import { StyleSheet } from "react-native-unistyles";
11
+
12
+ export function SignIn() {
13
+ const [email, setEmail] = useState("");
14
+ const [password, setPassword] = useState("");
15
+ const [isLoading, setIsLoading] = useState(false);
16
+ const [error, setError] = useState<string | null>(null);
17
+
18
+ const handleLogin = async () => {
19
+ setIsLoading(true);
20
+ setError(null);
21
+
22
+ await authClient.signIn.email(
23
+ {
24
+ email,
25
+ password,
26
+ },
27
+ {
28
+ onError: (error) => {
29
+ setError(error.error?.message || "Failed to sign in");
30
+ setIsLoading(false);
31
+ },
32
+ onSuccess: () => {
33
+ setEmail("");
34
+ setPassword("");
35
+ },
36
+ onFinished: () => {
37
+ setIsLoading(false);
38
+ },
39
+ },
40
+ );
41
+ };
42
+
43
+ return (
44
+ <View style={styles.container}>
45
+ <Text style={styles.title}>Sign In</Text>
46
+
47
+ {error && (
48
+ <View style={styles.errorContainer}>
49
+ <Text style={styles.errorText}>{error}</Text>
50
+ </View>
51
+ )}
52
+
53
+ <TextInput
54
+ style={styles.input}
55
+ placeholder="Email"
56
+ value={email}
57
+ onChangeText={setEmail}
58
+ keyboardType="email-address"
59
+ autoCapitalize="none"
60
+ />
61
+
62
+ <TextInput
63
+ style={styles.input}
64
+ placeholder="Password"
65
+ value={password}
66
+ onChangeText={setPassword}
67
+ secureTextEntry
68
+ />
69
+
70
+ <TouchableOpacity
71
+ onPress={handleLogin}
72
+ disabled={isLoading}
73
+ style={styles.button}
74
+ >
75
+ {isLoading ? (
76
+ <ActivityIndicator size="small" color="#fff" />
77
+ ) : (
78
+ <Text style={styles.buttonText}>Sign In</Text>
79
+ )}
80
+ </TouchableOpacity>
81
+ </View>
82
+ );
83
+ }
84
+
85
+ const styles = StyleSheet.create((theme) => ({
86
+ container: {
87
+ marginTop: 24,
88
+ padding: 16,
89
+ borderRadius: 8,
90
+ borderWidth: 1,
91
+ borderColor: theme.colors.border,
92
+ },
93
+ title: {
94
+ fontSize: 18,
95
+ fontWeight: "600",
96
+ color: theme.colors.typography,
97
+ marginBottom: 16,
98
+ },
99
+ errorContainer: {
100
+ marginBottom: 16,
101
+ padding: 12,
102
+ borderRadius: 6,
103
+ },
104
+ errorText: {
105
+ color: theme.colors.destructive,
106
+ fontSize: 14,
107
+ },
108
+ input: {
109
+ marginBottom: 12,
110
+ padding: 16,
111
+ borderRadius: 6,
112
+ color: theme.colors.typography,
113
+ borderWidth: 1,
114
+ borderColor: theme.colors.border,
115
+ },
116
+ button: {
117
+ backgroundColor: theme.colors.primary,
118
+ padding: 16,
119
+ borderRadius: 6,
120
+ flexDirection: "row",
121
+ justifyContent: "center",
122
+ alignItems: "center",
123
+ },
124
+ buttonText: {
125
+ fontWeight: "500",
126
+ },
127
+ }));