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.
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{src-DeOVz-ZI.js → src-Nl2APXCU.js} +75 -17
- package/package.json +1 -1
- package/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs +50 -33
- 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/auth/better-auth/web/react/next/src/app/dashboard/page.tsx.hbs +11 -0
- package/templates/auth/better-auth/web/react/tanstack-start/src/middleware/auth.ts.hbs +20 -1
- package/templates/frontend/native/nativewind/app/(drawer)/index.tsx.hbs +151 -100
- package/templates/frontend/native/nativewind/app/_layout.tsx.hbs +21 -0
- package/templates/frontend/native/unistyles/app/(drawer)/index.tsx.hbs +201 -91
- package/templates/frontend/native/unistyles/app/_layout.tsx.hbs +28 -0
- /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",
|
|
@@ -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 = "
|
|
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 = "
|
|
1400
|
-
const POSTHOG_HOST = "
|
|
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
|
|
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");
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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 "
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
+
}));
|