create-better-t-stack 3.1.8 → 3.2.1

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 (27) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/index.js +1 -1
  3. package/dist/{src-DeOVz-ZI.js → src-DaKmcEvQ.js} +75 -19
  4. package/package.json +1 -1
  5. package/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs +55 -34
  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/frontend/native/nativewind/app/(drawer)/index.tsx.hbs +127 -76
  14. package/templates/frontend/native/nativewind/app/_layout.tsx.hbs +21 -0
  15. package/templates/frontend/native/unistyles/app/(drawer)/index.tsx.hbs +160 -51
  16. package/templates/frontend/native/unistyles/app/_layout.tsx.hbs +28 -0
  17. package/templates/frontend/native/unistyles/app.json.hbs +1 -1
  18. /package/templates/frontend/native/{native-base → base}/assets/images/android-icon-background.png +0 -0
  19. /package/templates/frontend/native/{native-base → base}/assets/images/android-icon-foreground.png +0 -0
  20. /package/templates/frontend/native/{native-base → base}/assets/images/android-icon-monochrome.png +0 -0
  21. /package/templates/frontend/native/{native-base → base}/assets/images/favicon.png +0 -0
  22. /package/templates/frontend/native/{native-base → base}/assets/images/icon.png +0 -0
  23. /package/templates/frontend/native/{native-base → base}/assets/images/partial-react-logo.png +0 -0
  24. /package/templates/frontend/native/{native-base → base}/assets/images/react-logo.png +0 -0
  25. /package/templates/frontend/native/{native-base → base}/assets/images/react-logo@2x.png +0 -0
  26. /package/templates/frontend/native/{native-base → base}/assets/images/react-logo@3x.png +0 -0
  27. /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-DaKmcEvQ.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-DaKmcEvQ.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",
@@ -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");
@@ -4636,7 +4674,8 @@ async function setupEnvironmentVariables(config) {
4636
4674
  const hasNuxt = frontend.includes("nuxt");
4637
4675
  const hasSvelte = frontend.includes("svelte");
4638
4676
  const hasSolid = frontend.includes("solid");
4639
- if (hasReactRouter || hasTanStackRouter || hasTanStackStart || hasNextJs || hasNuxt || hasSolid || hasSvelte) {
4677
+ const hasWebFrontend$1 = hasReactRouter || hasTanStackRouter || hasTanStackStart || hasNextJs || hasNuxt || hasSolid || hasSvelte;
4678
+ if (hasWebFrontend$1) {
4640
4679
  const clientDir = path.join(projectDir, "apps/web");
4641
4680
  if (await fs.pathExists(clientDir)) {
4642
4681
  const baseVar = getClientServerVar(frontend, backend);
@@ -4709,6 +4748,11 @@ async function setupEnvironmentVariables(config) {
4709
4748
  value: "",
4710
4749
  condition: true
4711
4750
  });
4751
+ if (backend === "convex" && auth === "better-auth") nativeVars.push({
4752
+ key: "EXPO_PUBLIC_CONVEX_SITE_URL",
4753
+ value: "https://<YOUR_CONVEX_URL>",
4754
+ condition: true
4755
+ });
4712
4756
  await addEnvVariablesToFile(path.join(nativeDir, ".env"), nativeVars);
4713
4757
  }
4714
4758
  }
@@ -4717,12 +4761,23 @@ async function setupEnvironmentVariables(config) {
4717
4761
  const convexBackendDir = path.join(projectDir, "packages/backend");
4718
4762
  if (await fs.pathExists(convexBackendDir)) {
4719
4763
  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
4764
+ const hasNative = frontend.includes("native-nativewind") || frontend.includes("native-unistyles");
4765
+ const hasWeb = hasWebFrontend$1;
4766
+ if (!await fs.pathExists(envLocalPath) || !(await fs.readFile(envLocalPath, "utf8")).includes("npx convex env set")) {
4767
+ const convexCommands = `# Set Convex environment variables
4721
4768
  # npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
4722
- # npx convex env set SITE_URL http://localhost:3001
4723
-
4724
- `);
4725
- await addEnvVariablesToFile(envLocalPath, [{
4769
+ ${hasWeb ? "# npx convex env set SITE_URL http://localhost:3001\n" : ""}
4770
+ `;
4771
+ await fs.appendFile(envLocalPath, convexCommands);
4772
+ }
4773
+ const convexBackendVars = [];
4774
+ if (hasNative) convexBackendVars.push({
4775
+ key: "EXPO_PUBLIC_CONVEX_SITE_URL",
4776
+ value: "",
4777
+ condition: true,
4778
+ comment: "Same as CONVEX_URL but ends in .site"
4779
+ });
4780
+ if (hasWeb) convexBackendVars.push({
4726
4781
  key: hasNextJs ? "NEXT_PUBLIC_CONVEX_SITE_URL" : "VITE_CONVEX_SITE_URL",
4727
4782
  value: "",
4728
4783
  condition: true,
@@ -4731,7 +4786,8 @@ async function setupEnvironmentVariables(config) {
4731
4786
  key: "SITE_URL",
4732
4787
  value: "http://localhost:3001",
4733
4788
  condition: true
4734
- }]);
4789
+ });
4790
+ await addEnvVariablesToFile(envLocalPath, convexBackendVars);
4735
4791
  }
4736
4792
  }
4737
4793
  return;
@@ -6612,9 +6668,9 @@ function getWranglerDeployInstructions(runCmd, webDeploy, serverDeploy, backend)
6612
6668
  const instructions = [];
6613
6669
  if (webDeploy === "wrangler") {
6614
6670
  const deployPath = backend === "self" ? "apps/web" : "apps/web";
6615
- instructions.push(`${pc.bold("Deploy web to Cloudflare Workers:")}\n${pc.cyan("•")} Deploy: ${`cd ${deployPath} && ${runCmd} run deploy`}`);
6671
+ instructions.push(`${pc.bold("Deploy web to Cloudflare Workers:")}\n${pc.cyan("•")} Deploy: ${`cd ${deployPath} && ${runCmd} deploy`}`);
6616
6672
  }
6617
- if (serverDeploy === "wrangler" && backend !== "self") instructions.push(`${pc.bold("Deploy server to Cloudflare Workers:")}\n${pc.cyan("•")} Deploy: ${`cd apps/server && ${runCmd} run deploy`}`);
6673
+ if (serverDeploy === "wrangler" && backend !== "self") instructions.push(`${pc.bold("Deploy server to Cloudflare Workers:")}\n${pc.cyan("•")} Deploy: ${`cd apps/server && ${runCmd} deploy`}`);
6618
6674
  return instructions.length ? `\n${instructions.join("\n")}` : "";
6619
6675
  }
6620
6676
  function getClerkInstructions() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "3.1.8",
3
+ "version": "3.2.1",
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,49 +1,70 @@
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
3
  import { convex } from "@convex-dev/better-auth/plugins";
4
+ import { expo } from "@better-auth/expo";
4
5
  {{else}}
5
- import { convex, crossDomain } from "@convex-dev/better-auth/plugins";
6
+ import { convex } from "@convex-dev/better-auth/plugins";
7
+ {{/if}}
8
+ {{#if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes frontend "svelte") (includes frontend "solid"))}}
9
+ import { crossDomain } from "@convex-dev/better-auth/plugins";
6
10
  {{/if}}
7
11
  import { components } from "./_generated/api";
8
12
  import { DataModel } from "./_generated/dataModel";
9
13
  import { query } from "./_generated/server";
10
14
  import { betterAuth } from "better-auth";
15
+ import { v } from "convex/values";
11
16
 
17
+ {{#if (or (includes frontend "tanstack-start") (includes frontend "next") (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes frontend "svelte") (includes frontend "solid"))}}
12
18
  const siteUrl = process.env.SITE_URL!;
19
+ {{/if}}
20
+ {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
21
+ const nativeAppUrl = process.env.NATIVE_APP_URL || "mybettertapp://";
22
+ {{/if}}
13
23
 
14
24
  export const authComponent = createClient<DataModel>(components.betterAuth);
15
25
 
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
- };
26
+ function createAuth(
27
+ ctx: GenericCtx<DataModel>,
28
+ { optionsOnly }: { optionsOnly?: boolean } = { optionsOnly: false }
29
+ ) {
30
+ return betterAuth({
31
+ logger: {
32
+ disabled: optionsOnly,
33
+ },
34
+ {{#if (and (or (includes frontend "native-nativewind") (includes frontend "native-unistyles")) (or (includes frontend "tanstack-start") (includes frontend "next")))}}
35
+ baseURL: siteUrl,
36
+ trustedOrigins: [siteUrl, nativeAppUrl],
37
+ {{else if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
38
+ trustedOrigins: [nativeAppUrl],
39
+ {{else if (or (includes frontend "tanstack-start") (includes frontend "next"))}}
40
+ baseURL: siteUrl,
41
+ trustedOrigins: [siteUrl],
42
+ {{else}}
43
+ trustedOrigins: [siteUrl],
44
+ {{/if}}
45
+ database: authComponent.adapter(ctx),
46
+ emailAndPassword: {
47
+ enabled: true,
48
+ requireEmailVerification: false,
49
+ },
50
+ plugins: [
51
+ {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
52
+ expo(),
53
+ {{/if}}
54
+ {{#if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes frontend "svelte") (includes frontend "solid"))}}
55
+ crossDomain({ siteUrl }),
56
+ {{/if}}
57
+ convex(),
58
+ ],
59
+ });
60
+ }
61
+
62
+ export { createAuth };
43
63
 
44
64
  export const getCurrentUser = query({
45
- args: {},
46
- handler: async (ctx) => {
47
- return authComponent.getAuthUser(ctx);
48
- },
49
- });
65
+ args: {},
66
+ returns: v.any(),
67
+ handler: async function (ctx, args) {
68
+ return authComponent.getAuthUser(ctx);
69
+ },
70
+ });
@@ -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
+ }));