create-better-t-stack 3.1.6 → 3.1.8-canary.65dbf382

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-EpWSiOd8.js} +51 -13
  4. package/package.json +1 -1
  5. package/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs +45 -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
@@ -0,0 +1,145 @@
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 SignUp() {
13
+ const [name, setName] = useState("");
14
+ const [email, setEmail] = useState("");
15
+ const [password, setPassword] = useState("");
16
+ const [isLoading, setIsLoading] = useState(false);
17
+ const [error, setError] = useState<string | null>(null);
18
+
19
+ const handleSignUp = async () => {
20
+ setIsLoading(true);
21
+ setError(null);
22
+
23
+ await authClient.signUp.email(
24
+ {
25
+ name,
26
+ email,
27
+ password,
28
+ },
29
+ {
30
+ onError: (error) => {
31
+ setError(error.error?.message || "Failed to sign up");
32
+ setIsLoading(false);
33
+ },
34
+ onSuccess: () => {
35
+ setName("");
36
+ setEmail("");
37
+ setPassword("");
38
+ },
39
+ onFinished: () => {
40
+ setIsLoading(false);
41
+ },
42
+ },
43
+ );
44
+ };
45
+
46
+ return (
47
+ <View style={styles.container}>
48
+ <Text style={styles.title}>Create Account</Text>
49
+
50
+ {error && (
51
+ <View style={styles.errorContainer}>
52
+ <Text style={styles.errorText}>{error}</Text>
53
+ </View>
54
+ )}
55
+
56
+ <TextInput
57
+ style={styles.input}
58
+ placeholder="Name"
59
+ value={name}
60
+ onChangeText={setName}
61
+ />
62
+
63
+ <TextInput
64
+ style={styles.input}
65
+ placeholder="Email"
66
+ value={email}
67
+ onChangeText={setEmail}
68
+ keyboardType="email-address"
69
+ autoCapitalize="none"
70
+ />
71
+
72
+ <TextInput
73
+ style={styles.inputLast}
74
+ placeholder="Password"
75
+ value={password}
76
+ onChangeText={setPassword}
77
+ secureTextEntry
78
+ />
79
+
80
+ <TouchableOpacity
81
+ onPress={handleSignUp}
82
+ disabled={isLoading}
83
+ style={styles.button}
84
+ >
85
+ {isLoading ? (
86
+ <ActivityIndicator size="small" color="#fff" />
87
+ ) : (
88
+ <Text style={styles.buttonText}>Sign Up</Text>
89
+ )}
90
+ </TouchableOpacity>
91
+ </View>
92
+ );
93
+ }
94
+
95
+ const styles = StyleSheet.create((theme) => ({
96
+ container: {
97
+ marginTop: 24,
98
+ padding: 16,
99
+ borderRadius: 8,
100
+ borderWidth: 1,
101
+ borderColor: theme.colors.border,
102
+ },
103
+ title: {
104
+ fontSize: 18,
105
+ fontWeight: "600",
106
+ color: theme.colors.typography,
107
+ marginBottom: 16,
108
+ },
109
+ errorContainer: {
110
+ marginBottom: 16,
111
+ padding: 12,
112
+ borderRadius: 6,
113
+ },
114
+ errorText: {
115
+ color: theme.colors.destructive,
116
+ fontSize: 14,
117
+ },
118
+ input: {
119
+ marginBottom: 12,
120
+ padding: 16,
121
+ borderRadius: 6,
122
+ color: theme.colors.typography,
123
+ borderWidth: 1,
124
+ borderColor: theme.colors.border,
125
+ },
126
+ inputLast: {
127
+ marginBottom: 16,
128
+ padding: 16,
129
+ borderRadius: 6,
130
+ color: theme.colors.typography,
131
+ borderWidth: 1,
132
+ borderColor: theme.colors.border,
133
+ },
134
+ button: {
135
+ backgroundColor: theme.colors.primary,
136
+ padding: 16,
137
+ borderRadius: 6,
138
+ flexDirection: "row",
139
+ justifyContent: "center",
140
+ alignItems: "center",
141
+ },
142
+ buttonText: {
143
+ fontWeight: "500",
144
+ },
145
+ }));
@@ -1,13 +1,14 @@
1
1
  import { expoClient } from "@better-auth/expo/client";
2
2
  import { createAuthClient } from "better-auth/react";
3
3
  import * as SecureStore from "expo-secure-store";
4
+ import Constants from "expo-constants";
4
5
 
5
6
  export const authClient = createAuthClient({
6
7
  baseURL: process.env.EXPO_PUBLIC_SERVER_URL,
7
8
  plugins: [
8
9
  expoClient({
9
- scheme: "mybettertapp",
10
- storagePrefix: "{{projectName}}",
10
+ scheme: Constants.expoConfig?.scheme as string,
11
+ storagePrefix: Constants.expoConfig?.scheme as string,
11
12
  storage: SecureStore,
12
13
  }),
13
14
  ],
@@ -1,13 +1,24 @@
1
1
  import { redirect } from "next/navigation";
2
2
  import Dashboard from "./dashboard";
3
3
  import { headers } from "next/headers";
4
+ {{#if (eq backend "self")}}
4
5
  import { auth } from "@{{projectName}}/auth";
6
+ {{/if}}
5
7
  import { authClient } from "@/lib/auth-client";
6
8
 
7
9
  export default async function DashboardPage() {
10
+ {{#if (eq backend "self")}}
8
11
  const session = await auth.api.getSession({
9
12
  headers: await headers(),
10
13
  });
14
+ {{else}}
15
+ const session = await authClient.getSession({
16
+ fetchOptions: {
17
+ headers: await headers(),
18
+ throw: true
19
+ }
20
+ });
21
+ {{/if}}
11
22
 
12
23
  if (!session?.user) {
13
24
  redirect("/login");
@@ -1,3 +1,4 @@
1
+ {{#if (eq backend "self")}}
1
2
  import { auth } from "@{{projectName}}/auth";
2
3
  import { createMiddleware } from "@tanstack/react-start";
3
4
 
@@ -9,4 +10,22 @@ export const authMiddleware = createMiddleware().server(async ({ next, request }
9
10
  return next({
10
11
  context: { session }
11
12
  })
12
- })
13
+ })
14
+ {{else}}
15
+ import { authClient } from "@/lib/auth-client";
16
+ import { createMiddleware } from "@tanstack/react-start";
17
+
18
+ export const authMiddleware = createMiddleware().server(
19
+ async ({ next, request }) => {
20
+ const session = await authClient.getSession({
21
+ fetchOptions: {
22
+ headers: request.headers,
23
+ throw: true
24
+ }
25
+ })
26
+ return next({
27
+ context: { session },
28
+ });
29
+ },
30
+ );
31
+ {{/if}}
@@ -1,4 +1,4 @@
1
- import { View, Text, ScrollView } from "react-native";
1
+ import { View, Text, ScrollView, TouchableOpacity } from "react-native";
2
2
  import { Container } from "@/components/container";
3
3
  {{#if (eq api "orpc")}}
4
4
  import { useQuery } from "@tanstack/react-query";
@@ -16,112 +16,163 @@ import { api } from "@{{ projectName }}/backend/convex/_generated/api";
16
16
  import { useUser } from "@clerk/clerk-expo";
17
17
  import { SignOutButton } from "@/components/sign-out-button";
18
18
  {{else}}
19
+ {{#if (eq auth "better-auth")}}
20
+ import { useConvexAuth, useQuery } from "convex/react";
21
+ import { api } from "@{{ projectName }}/backend/convex/_generated/api";
22
+ import { authClient } from "@/lib/auth-client";
23
+ import { SignIn } from "@/components/sign-in";
24
+ import { SignUp } from "@/components/sign-up";
25
+ {{else}}
19
26
  import { useQuery } from "convex/react";
20
27
  import { api } from "@{{ projectName }}/backend/convex/_generated/api";
21
28
  {{/if}}
22
29
  {{/if}}
30
+ {{/if}}
23
31
 
24
32
  export default function Home() {
25
- {{#if (eq api "orpc")}}
26
- const healthCheck = useQuery(orpc.healthCheck.queryOptions());
27
- {{/if}}
28
- {{#if (eq api "trpc")}}
29
- const healthCheck = useQuery(trpc.healthCheck.queryOptions());
30
- {{/if}}
31
- {{#if (eq backend "convex")}}
32
- {{#if (eq auth "clerk")}}
33
- const { user } = useUser();
34
- const healthCheck = useQuery(api.healthCheck.get);
35
- const privateData = useQuery(api.privateData.get);
36
- {{else}}
37
- const healthCheck = useQuery(api.healthCheck.get);
38
- {{/if}}
39
- {{/if}}
33
+ {{#if (eq api "orpc")}}
34
+ const healthCheck = useQuery(orpc.healthCheck.queryOptions());
35
+ {{/if}}
36
+ {{#if (eq api "trpc")}}
37
+ const healthCheck = useQuery(trpc.healthCheck.queryOptions());
38
+ {{/if}}
39
+ {{#if (eq backend "convex")}}
40
+ {{#if (eq auth "clerk")}}
41
+ const { user } = useUser();
42
+ const healthCheck = useQuery(api.healthCheck.get);
43
+ const privateData = useQuery(api.privateData.get);
44
+ {{else}}
45
+ {{#if (eq auth "better-auth")}}
46
+ const healthCheck = useQuery(api.healthCheck.get);
47
+ const { isAuthenticated } = useConvexAuth();
48
+ const user = useQuery(api.auth.getCurrentUser, isAuthenticated ? {} : "skip");
49
+ {{else}}
50
+ const healthCheck = useQuery(api.healthCheck.get);
51
+ {{/if}}
52
+ {{/if}}
53
+ {{/if}}
40
54
 
41
- return (
42
- <Container>
43
- <ScrollView showsVerticalScrollIndicator={false} className="flex-1">
44
- <Text className="font-mono text-foreground text-3xl font-bold mb-4">
45
- BETTER T STACK
46
- </Text>
47
- <View className="bg-card border border-border rounded-xl p-6 mb-6 shadow-sm">
48
- {{#if (eq backend "convex")}}
49
- <View className="flex-row items-center gap-3">
50
- <View
51
- className={`h-3 w-3 rounded-full ${
52
- healthCheck ? "bg-green-500" : "bg-orange-500"
53
- }`}
54
- />
55
- <View className="flex-1">
56
- <Text className="text-sm font-medium text-card-foreground">
57
- Convex
58
- </Text>
59
- <Text className="text-xs text-muted-foreground">
60
- {healthCheck === undefined
61
- ? "Checking connection..."
62
- : healthCheck === "OK"
63
- ? "All systems operational"
64
- : "Service unavailable"}
65
- </Text>
66
- </View>
55
+ return (
56
+ <Container>
57
+ <ScrollView className="flex-1">
58
+ <View className="px-4">
59
+ <Text className="font-mono text-foreground text-3xl font-bold mb-4">
60
+ BETTER T STACK
61
+ </Text>
62
+ <View className="bg-card border border-border rounded-xl p-6 mb-6 shadow-sm">
63
+ {{#if (eq backend "convex")}}
64
+ {{#unless (eq auth "better-auth")}}
65
+ <View className="flex-row items-center gap-3">
66
+ <View className={`h-3 w-3 rounded-full ${ healthCheck ? "bg-green-500" : "bg-orange-500" }`} />
67
+ <View className="flex-1">
68
+ <Text className="text-sm font-medium text-card-foreground">
69
+ Convex
70
+ </Text>
71
+ <Text className="text-xs text-muted-foreground">
72
+ {healthCheck === undefined
73
+ ? "Checking connection..."
74
+ : healthCheck === "OK"
75
+ ? "All systems operational"
76
+ : "Service unavailable"}
77
+ </Text>
78
+ </View>
79
+ </View>
80
+ {{/unless}}
81
+ {{else}}
82
+ {{#unless (eq api "none")}}
83
+ <View className="flex-row items-center gap-3">
84
+ <View className={`h-3 w-3 rounded-full ${ healthCheck.data ? "bg-green-500" : "bg-orange-500" }`} />
85
+ <View className="flex-1">
86
+ <Text className="text-sm font-medium text-card-foreground">
87
+ {{#if (eq api "orpc")}}
88
+ ORPC
89
+ {{/if}}
90
+ {{#if (eq api "trpc")}}
91
+ TRPC
92
+ {{/if}}
93
+ </Text>
94
+ <Text className="text-xs text-muted-foreground">
95
+ {{#if (eq api "orpc")}}
96
+ {healthCheck.isLoading
97
+ ? "Checking connection..."
98
+ : healthCheck.data
99
+ ? "All systems operational"
100
+ : "Service unavailable"}
101
+ {{/if}}
102
+ {{#if (eq api "trpc")}}
103
+ {healthCheck.isLoading
104
+ ? "Checking connection..."
105
+ : healthCheck.data
106
+ ? "All systems operational"
107
+ : "Service unavailable"}
108
+ {{/if}}
109
+ </Text>
67
110
  </View>
68
- {{else}}
69
- {{#unless (eq api "none")}}
70
- <View className="flex-row items-center gap-3">
71
- <View
72
- className={`h-3 w-3 rounded-full ${
73
- healthCheck.data ? "bg-green-500" : "bg-orange-500"
74
- }`}
75
- />
76
- <View className="flex-1">
77
- <Text className="text-sm font-medium text-card-foreground">
78
- {{#if (eq api "orpc")}}
79
- ORPC
80
- {{/if}}
81
- {{#if (eq api "trpc")}}
82
- TRPC
83
- {{/if}}
84
- </Text>
85
- <Text className="text-xs text-muted-foreground">
86
- {{#if (eq api "orpc")}}
87
- {healthCheck.isLoading
88
- ? "Checking connection..."
89
- : healthCheck.data
90
- ? "All systems operational"
91
- : "Service unavailable"}
92
- {{/if}}
93
- {{#if (eq api "trpc")}}
94
- {healthCheck.isLoading
95
- ? "Checking connection..."
96
- : healthCheck.data
97
- ? "All systems operational"
98
- : "Service unavailable"}
99
- {{/if}}
100
- </Text>
101
- </View>
102
- </View>
103
- {{/unless}}
104
- {{/if}}
105
111
  </View>
106
- {{#if (and (eq backend "convex") (eq auth "clerk"))}}
107
- <Authenticated>
108
- <Text>Hello {user?.emailAddresses[0].emailAddress}</Text>
109
- <Text>Private Data: {privateData?.message}</Text>
110
- <SignOutButton />
111
- </Authenticated>
112
- <Unauthenticated>
113
- <Link href="/(auth)/sign-in">
114
- <Text>Sign in</Text>
115
- </Link>
116
- <Link href="/(auth)/sign-up">
117
- <Text>Sign up</Text>
118
- </Link>
119
- </Unauthenticated>
120
- <AuthLoading>
121
- <Text>Loading...</Text>
122
- </AuthLoading>
112
+ {{/unless}}
123
113
  {{/if}}
124
- </ScrollView>
125
- </Container>
126
- );
127
- }
114
+ </View>
115
+ {{#if (and (eq backend "convex") (eq auth "clerk"))}}
116
+ <Authenticated>
117
+ <Text>Hello {user?.emailAddresses[0].emailAddress}</Text>
118
+ <Text>Private Data: {privateData?.message}</Text>
119
+ <SignOutButton />
120
+ </Authenticated>
121
+ <Unauthenticated>
122
+ <Link href="/(auth)/sign-in">
123
+ <Text>Sign in</Text>
124
+ </Link>
125
+ <Link href="/(auth)/sign-up">
126
+ <Text>Sign up</Text>
127
+ </Link>
128
+ </Unauthenticated>
129
+ <AuthLoading>
130
+ <Text>Loading...</Text>
131
+ </AuthLoading>
132
+ {{/if}}
133
+ {{#if (and (eq backend "convex") (eq auth "better-auth"))}}
134
+ {user ? (
135
+ <View className="mb-6 p-4 bg-card rounded-lg border border-border">
136
+ <View className="flex-row justify-between items-center mb-2">
137
+ <Text className="text-foreground text-base">
138
+ Welcome,{" "}
139
+ <Text className="font-medium">{user.name}</Text>
140
+ </Text>
141
+ </View>
142
+ <Text className="text-muted-foreground text-sm mb-4">
143
+ {user.email}
144
+ </Text>
145
+
146
+ <TouchableOpacity className="bg-destructive py-2 px-4 rounded-md self-start" onPress={()=> {
147
+ authClient.signOut();
148
+ }}
149
+ >
150
+ <Text className="text-white font-medium">Sign Out</Text>
151
+ </TouchableOpacity>
152
+ </View>
153
+ ) : null}
154
+ <View className="mb-6 rounded-lg border border-border p-4">
155
+ <Text className="mb-3 font-medium text-foreground">API Status</Text>
156
+ <View className="flex-row items-center gap-2">
157
+ <View className={`h-3 w-3 rounded-full ${healthCheck ? "bg-green-500" : "bg-red-500" }`} />
158
+ <Text className="text-muted-foreground">
159
+ {healthCheck === undefined
160
+ ? "Checking..."
161
+ : healthCheck === "OK"
162
+ ? "Connected to API"
163
+ : "API Disconnected"}
164
+ </Text>
165
+ </View>
166
+ </View>
167
+ {!user && (
168
+ <>
169
+ <SignIn />
170
+ <SignUp />
171
+ </>
172
+ )}
173
+ {{/if}}
174
+ </View>
175
+ </ScrollView>
176
+ </Container>
177
+ );
178
+ }
@@ -2,7 +2,13 @@
2
2
  import "@/polyfills";
3
3
  {{/if}}
4
4
  {{#if (eq backend "convex")}}
5
+ {{#if (eq auth "better-auth")}}
6
+ import { ConvexReactClient } from "convex/react";
7
+ import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react";
8
+ import { authClient } from "@/lib/auth-client";
9
+ {{else}}
5
10
  import { ConvexProvider, ConvexReactClient } from "convex/react";
11
+ {{/if}}
6
12
  {{#if (eq auth "clerk")}}
7
13
  import { ClerkProvider, useAuth } from "@clerk/clerk-expo";
8
14
  import { ConvexProviderWithClerk } from "convex/react-clerk";
@@ -98,6 +104,21 @@ export default function RootLayout() {
98
104
  </ThemeProvider>
99
105
  </ConvexProviderWithClerk>
100
106
  </ClerkProvider>
107
+ {{else if (eq auth "better-auth")}}
108
+ <ConvexBetterAuthProvider client={convex} authClient={authClient}>
109
+ <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>
110
+ <StatusBar style={isDarkColorScheme ? "light" : "dark"} />
111
+ <GestureHandlerRootView style=\{{ flex: 1 }}>
112
+ <Stack>
113
+ <Stack.Screen name="(drawer)" options=\{{ headerShown: false }} />
114
+ <Stack.Screen
115
+ name="modal"
116
+ options=\{{ title: "Modal", presentation: "modal" }}
117
+ />
118
+ </Stack>
119
+ </GestureHandlerRootView>
120
+ </ThemeProvider>
121
+ </ConvexBetterAuthProvider>
101
122
  {{else}}
102
123
  <ConvexProvider client={convex}>
103
124
  <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>