create-better-t-stack 3.1.8 → 3.2.0
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.
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{src-DeOVz-ZI.js → src-JAo0_3IZ.js} +73 -17
- package/package.json +1 -1
- package/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs +55 -34
- package/templates/auth/better-auth/convex/backend/convex/http.ts.hbs +1 -1
- package/templates/auth/better-auth/convex/native/base/lib/auth-client.ts.hbs +19 -0
- package/templates/auth/better-auth/convex/native/nativewind/components/sign-in.tsx.hbs +86 -0
- package/templates/auth/better-auth/convex/native/nativewind/components/sign-up.tsx.hbs +97 -0
- package/templates/auth/better-auth/convex/native/unistyles/components/sign-in.tsx.hbs +127 -0
- package/templates/auth/better-auth/convex/native/unistyles/components/sign-up.tsx.hbs +145 -0
- package/templates/auth/better-auth/native/{native-base → base}/lib/auth-client.ts.hbs +3 -2
- package/templates/frontend/native/nativewind/app/(drawer)/index.tsx.hbs +127 -76
- package/templates/frontend/native/nativewind/app/_layout.tsx.hbs +21 -0
- package/templates/frontend/native/unistyles/app/(drawer)/index.tsx.hbs +160 -51
- package/templates/frontend/native/unistyles/app/_layout.tsx.hbs +28 -0
- package/templates/frontend/native/unistyles/app.json.hbs +1 -1
- /package/templates/frontend/native/{native-base → base}/assets/images/android-icon-background.png +0 -0
- /package/templates/frontend/native/{native-base → base}/assets/images/android-icon-foreground.png +0 -0
- /package/templates/frontend/native/{native-base → base}/assets/images/android-icon-monochrome.png +0 -0
- /package/templates/frontend/native/{native-base → base}/assets/images/favicon.png +0 -0
- /package/templates/frontend/native/{native-base → base}/assets/images/icon.png +0 -0
- /package/templates/frontend/native/{native-base → base}/assets/images/partial-react-logo.png +0 -0
- /package/templates/frontend/native/{native-base → base}/assets/images/react-logo.png +0 -0
- /package/templates/frontend/native/{native-base → base}/assets/images/react-logo@2x.png +0 -0
- /package/templates/frontend/native/{native-base → base}/assets/images/react-logo@3x.png +0 -0
- /package/templates/frontend/native/{native-base → base}/assets/images/splash-icon.png +0 -0
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -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
|
|
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/
|
|
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/
|
|
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
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/package.json
CHANGED
|
@@ -1,49 +1,70 @@
|
|
|
1
1
|
import { createClient, type GenericCtx } from "@convex-dev/better-auth";
|
|
2
|
-
{{#if (or (includes frontend "
|
|
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
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
+
}));
|