@better-t-stack/template-generator 3.20.1 → 3.21.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/index.d.mts +3 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2158 -990
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -705,7 +705,10 @@ const dependencyVersionMap = {
|
|
|
705
705
|
"@tanstack/vue-query": "^5.90.2",
|
|
706
706
|
"@tanstack/react-query-devtools": "^5.91.1",
|
|
707
707
|
"@tanstack/react-query": "^5.90.12",
|
|
708
|
+
"@tanstack/react-form": "^1.28.0",
|
|
708
709
|
"@tanstack/react-router-ssr-query": "^1.142.7",
|
|
710
|
+
"@tanstack/solid-form": "^1.28.0",
|
|
711
|
+
"@tanstack/svelte-form": "^1.28.0",
|
|
709
712
|
"@tanstack/solid-query": "^5.87.4",
|
|
710
713
|
"@tanstack/solid-query-devtools": "^5.87.4",
|
|
711
714
|
"@tanstack/solid-router-devtools": "^1.131.44",
|
|
@@ -1261,6 +1264,9 @@ function processConvexAuthDeps(vfs, config) {
|
|
|
1261
1264
|
const hasNextJs = frontend.includes("next");
|
|
1262
1265
|
const hasTanStackStart = frontend.includes("tanstack-start");
|
|
1263
1266
|
const hasViteReact = frontend.some((f) => ["tanstack-router", "react-router"].includes(f));
|
|
1267
|
+
const hasSolid = frontend.includes("solid");
|
|
1268
|
+
const hasSvelte = frontend.includes("svelte");
|
|
1269
|
+
const hasReactWebAuthForms = hasNextJs || hasTanStackStart || hasViteReact;
|
|
1264
1270
|
if (auth === "clerk") {
|
|
1265
1271
|
if (webExists) {
|
|
1266
1272
|
if (hasNextJs) addPackageDependency({
|
|
@@ -1299,19 +1305,37 @@ function processConvexAuthDeps(vfs, config) {
|
|
|
1299
1305
|
customDependencies: { "@better-auth/expo": "1.4.9" }
|
|
1300
1306
|
});
|
|
1301
1307
|
}
|
|
1302
|
-
if (webExists)
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
+
if (webExists) {
|
|
1309
|
+
addPackageDependency({
|
|
1310
|
+
vfs,
|
|
1311
|
+
packagePath: webPath,
|
|
1312
|
+
dependencies: ["better-auth", "@convex-dev/better-auth"],
|
|
1313
|
+
customDependencies: { "better-auth": "1.4.9" }
|
|
1314
|
+
});
|
|
1315
|
+
if (hasReactWebAuthForms) addPackageDependency({
|
|
1316
|
+
vfs,
|
|
1317
|
+
packagePath: webPath,
|
|
1318
|
+
dependencies: ["@tanstack/react-form"]
|
|
1319
|
+
});
|
|
1320
|
+
if (hasSolid) addPackageDependency({
|
|
1321
|
+
vfs,
|
|
1322
|
+
packagePath: webPath,
|
|
1323
|
+
dependencies: ["@tanstack/solid-form"]
|
|
1324
|
+
});
|
|
1325
|
+
if (hasSvelte) addPackageDependency({
|
|
1326
|
+
vfs,
|
|
1327
|
+
packagePath: webPath,
|
|
1328
|
+
dependencies: ["@tanstack/svelte-form"]
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1308
1331
|
if (nativeExists && hasNative) addPackageDependency({
|
|
1309
1332
|
vfs,
|
|
1310
1333
|
packagePath: nativePath,
|
|
1311
1334
|
dependencies: [
|
|
1312
1335
|
"better-auth",
|
|
1313
1336
|
"@better-auth/expo",
|
|
1314
|
-
"@convex-dev/better-auth"
|
|
1337
|
+
"@convex-dev/better-auth",
|
|
1338
|
+
"@tanstack/react-form"
|
|
1315
1339
|
],
|
|
1316
1340
|
customDependencies: {
|
|
1317
1341
|
"better-auth": "1.4.9",
|
|
@@ -1343,6 +1367,14 @@ function processStandardAuthDeps(vfs, config) {
|
|
|
1343
1367
|
"solid",
|
|
1344
1368
|
"astro"
|
|
1345
1369
|
].includes(f));
|
|
1370
|
+
const hasReactWebAuthForms = frontend.some((f) => [
|
|
1371
|
+
"react-router",
|
|
1372
|
+
"tanstack-router",
|
|
1373
|
+
"tanstack-start",
|
|
1374
|
+
"next"
|
|
1375
|
+
].includes(f));
|
|
1376
|
+
const hasSolid = frontend.includes("solid");
|
|
1377
|
+
const hasSvelte = frontend.includes("svelte");
|
|
1346
1378
|
if (auth === "better-auth") {
|
|
1347
1379
|
if (authExists) {
|
|
1348
1380
|
addPackageDependency({
|
|
@@ -1356,15 +1388,36 @@ function processStandardAuthDeps(vfs, config) {
|
|
|
1356
1388
|
dependencies: ["@better-auth/expo"]
|
|
1357
1389
|
});
|
|
1358
1390
|
}
|
|
1359
|
-
if (hasWebFrontend && webExists)
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1391
|
+
if (hasWebFrontend && webExists) {
|
|
1392
|
+
addPackageDependency({
|
|
1393
|
+
vfs,
|
|
1394
|
+
packagePath: webPath,
|
|
1395
|
+
dependencies: ["better-auth"]
|
|
1396
|
+
});
|
|
1397
|
+
if (hasReactWebAuthForms) addPackageDependency({
|
|
1398
|
+
vfs,
|
|
1399
|
+
packagePath: webPath,
|
|
1400
|
+
dependencies: ["@tanstack/react-form"]
|
|
1401
|
+
});
|
|
1402
|
+
if (hasSolid) addPackageDependency({
|
|
1403
|
+
vfs,
|
|
1404
|
+
packagePath: webPath,
|
|
1405
|
+
dependencies: ["@tanstack/solid-form"]
|
|
1406
|
+
});
|
|
1407
|
+
if (hasSvelte) addPackageDependency({
|
|
1408
|
+
vfs,
|
|
1409
|
+
packagePath: webPath,
|
|
1410
|
+
dependencies: ["@tanstack/svelte-form"]
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1364
1413
|
if (hasNative && nativeExists) addPackageDependency({
|
|
1365
1414
|
vfs,
|
|
1366
1415
|
packagePath: nativePath,
|
|
1367
|
-
dependencies: [
|
|
1416
|
+
dependencies: [
|
|
1417
|
+
"better-auth",
|
|
1418
|
+
"@better-auth/expo",
|
|
1419
|
+
"@tanstack/react-form"
|
|
1420
|
+
]
|
|
1368
1421
|
});
|
|
1369
1422
|
}
|
|
1370
1423
|
}
|
|
@@ -1513,10 +1566,8 @@ function processPrismaDeps(vfs, config, dbPkgPath, webPkgPath, webExists) {
|
|
|
1513
1566
|
if (database === "mysql" && dbSetup === "planetscale") deps.push("@prisma/adapter-planetscale", "@planetscale/database");
|
|
1514
1567
|
else if (database === "mysql") deps.push("@prisma/adapter-mariadb");
|
|
1515
1568
|
else if (database === "sqlite") deps.push(dbSetup === "d1" ? "@prisma/adapter-d1" : "@prisma/adapter-libsql");
|
|
1516
|
-
else if (database === "postgres") if (dbSetup === "neon")
|
|
1517
|
-
|
|
1518
|
-
devDeps.push("@types/ws");
|
|
1519
|
-
} else if (dbSetup === "prisma-postgres") {
|
|
1569
|
+
else if (database === "postgres") if (dbSetup === "neon") deps.push("@prisma/adapter-neon", "@neondatabase/serverless");
|
|
1570
|
+
else if (dbSetup === "prisma-postgres") {
|
|
1520
1571
|
deps.push("@prisma/adapter-pg", "pg");
|
|
1521
1572
|
devDeps.push("@types/pg");
|
|
1522
1573
|
} else {
|
|
@@ -1556,10 +1607,8 @@ function processDrizzleDeps(vfs, config, dbPkgPath, webPkgPath, webExists) {
|
|
|
1556
1607
|
} else if (database === "postgres") {
|
|
1557
1608
|
const deps = ["drizzle-orm"];
|
|
1558
1609
|
const devDeps = ["drizzle-kit"];
|
|
1559
|
-
if (dbSetup === "neon")
|
|
1560
|
-
|
|
1561
|
-
devDeps.push("@types/ws");
|
|
1562
|
-
} else {
|
|
1610
|
+
if (dbSetup === "neon") deps.push("@neondatabase/serverless");
|
|
1611
|
+
else {
|
|
1563
1612
|
deps.push("pg");
|
|
1564
1613
|
devDeps.push("@types/pg");
|
|
1565
1614
|
}
|
|
@@ -5139,91 +5188,187 @@ export const get = query({
|
|
|
5139
5188
|
});
|
|
5140
5189
|
`],
|
|
5141
5190
|
["auth/better-auth/convex/native/bare/components/sign-in.tsx.hbs", `import { authClient } from "@/lib/auth-client";
|
|
5191
|
+
import { useForm } from "@tanstack/react-form";
|
|
5142
5192
|
import { useState } from "react";
|
|
5143
5193
|
import {
|
|
5144
5194
|
ActivityIndicator,
|
|
5195
|
+
StyleSheet,
|
|
5145
5196
|
Text,
|
|
5146
5197
|
TextInput,
|
|
5147
5198
|
TouchableOpacity,
|
|
5148
5199
|
View,
|
|
5149
|
-
StyleSheet,
|
|
5150
5200
|
} from "react-native";
|
|
5151
|
-
import { useColorScheme } from "@/lib/use-color-scheme";
|
|
5152
5201
|
import { NAV_THEME } from "@/lib/constants";
|
|
5202
|
+
import { useColorScheme } from "@/lib/use-color-scheme";
|
|
5203
|
+
import z from "zod";
|
|
5204
|
+
|
|
5205
|
+
const signInSchema = z.object({
|
|
5206
|
+
email: z
|
|
5207
|
+
.string()
|
|
5208
|
+
.trim()
|
|
5209
|
+
.min(1, "Email is required")
|
|
5210
|
+
.email("Enter a valid email address"),
|
|
5211
|
+
password: z
|
|
5212
|
+
.string()
|
|
5213
|
+
.min(1, "Password is required")
|
|
5214
|
+
.min(8, "Use at least 8 characters"),
|
|
5215
|
+
});
|
|
5216
|
+
|
|
5217
|
+
function getErrorMessage(error: unknown): string | null {
|
|
5218
|
+
if (!error) return null;
|
|
5219
|
+
|
|
5220
|
+
if (typeof error === "string") {
|
|
5221
|
+
return error;
|
|
5222
|
+
}
|
|
5223
|
+
|
|
5224
|
+
if (Array.isArray(error)) {
|
|
5225
|
+
for (const issue of error) {
|
|
5226
|
+
const message = getErrorMessage(issue);
|
|
5227
|
+
if (message) {
|
|
5228
|
+
return message;
|
|
5229
|
+
}
|
|
5230
|
+
}
|
|
5231
|
+
return null;
|
|
5232
|
+
}
|
|
5233
|
+
|
|
5234
|
+
if (typeof error === "object" && error !== null) {
|
|
5235
|
+
const maybeError = error as { message?: unknown };
|
|
5236
|
+
if (typeof maybeError.message === "string") {
|
|
5237
|
+
return maybeError.message;
|
|
5238
|
+
}
|
|
5239
|
+
}
|
|
5240
|
+
|
|
5241
|
+
return null;
|
|
5242
|
+
}
|
|
5153
5243
|
|
|
5154
5244
|
function SignIn() {
|
|
5155
5245
|
const { colorScheme } = useColorScheme();
|
|
5156
5246
|
const theme = colorScheme === "dark" ? NAV_THEME.dark : NAV_THEME.light;
|
|
5157
|
-
const [email, setEmail] = useState("");
|
|
5158
|
-
const [password, setPassword] = useState("");
|
|
5159
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
5160
5247
|
const [error, setError] = useState<string | null>(null);
|
|
5161
5248
|
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
},
|
|
5176
|
-
onSuccess() {
|
|
5177
|
-
setEmail("");
|
|
5178
|
-
setPassword("");
|
|
5249
|
+
const form = useForm({
|
|
5250
|
+
defaultValues: {
|
|
5251
|
+
email: "",
|
|
5252
|
+
password: "",
|
|
5253
|
+
},
|
|
5254
|
+
validators: {
|
|
5255
|
+
onSubmit: signInSchema,
|
|
5256
|
+
},
|
|
5257
|
+
onSubmit: async ({ value, formApi }) => {
|
|
5258
|
+
await authClient.signIn.email(
|
|
5259
|
+
{
|
|
5260
|
+
email: value.email.trim(),
|
|
5261
|
+
password: value.password,
|
|
5179
5262
|
},
|
|
5180
|
-
|
|
5181
|
-
|
|
5263
|
+
{
|
|
5264
|
+
onError(error) {
|
|
5265
|
+
setError(error.error?.message || "Failed to sign in");
|
|
5266
|
+
},
|
|
5267
|
+
onSuccess() {
|
|
5268
|
+
setError(null);
|
|
5269
|
+
formApi.reset();
|
|
5270
|
+
},
|
|
5182
5271
|
},
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
}
|
|
5272
|
+
);
|
|
5273
|
+
},
|
|
5274
|
+
});
|
|
5186
5275
|
|
|
5187
5276
|
return (
|
|
5188
5277
|
<View style={[styles.card, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
|
5189
5278
|
<Text style={[styles.title, { color: theme.text }]}>Sign In</Text>
|
|
5190
5279
|
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5280
|
+
<form.Subscribe
|
|
5281
|
+
selector={(state) => ({
|
|
5282
|
+
isSubmitting: state.isSubmitting,
|
|
5283
|
+
validationError: getErrorMessage(state.errorMap.onSubmit),
|
|
5284
|
+
})}
|
|
5285
|
+
>
|
|
5286
|
+
{({ isSubmitting, validationError }) => {
|
|
5287
|
+
const formError = error ?? validationError;
|
|
5196
5288
|
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
autoCapitalize="none"
|
|
5205
|
-
/>
|
|
5289
|
+
return (
|
|
5290
|
+
<>
|
|
5291
|
+
{formError ? (
|
|
5292
|
+
<View style={[styles.errorContainer, { backgroundColor: theme.notification + "20" }]}>
|
|
5293
|
+
<Text style={[styles.errorText, { color: theme.notification }]}>{formError}</Text>
|
|
5294
|
+
</View>
|
|
5295
|
+
) : null}
|
|
5206
5296
|
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5297
|
+
<form.Field name="email">
|
|
5298
|
+
{(field) => (
|
|
5299
|
+
<TextInput
|
|
5300
|
+
style={[
|
|
5301
|
+
styles.input,
|
|
5302
|
+
{
|
|
5303
|
+
color: theme.text,
|
|
5304
|
+
borderColor: theme.border,
|
|
5305
|
+
backgroundColor: theme.background,
|
|
5306
|
+
},
|
|
5307
|
+
]}
|
|
5308
|
+
placeholder="Email"
|
|
5309
|
+
placeholderTextColor={theme.text}
|
|
5310
|
+
value={field.state.value}
|
|
5311
|
+
onBlur={field.handleBlur}
|
|
5312
|
+
onChangeText={(value) => {
|
|
5313
|
+
field.handleChange(value);
|
|
5314
|
+
if (error) {
|
|
5315
|
+
setError(null);
|
|
5316
|
+
}
|
|
5317
|
+
}}
|
|
5318
|
+
keyboardType="email-address"
|
|
5319
|
+
autoCapitalize="none"
|
|
5320
|
+
/>
|
|
5321
|
+
)}
|
|
5322
|
+
</form.Field>
|
|
5215
5323
|
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5324
|
+
<form.Field name="password">
|
|
5325
|
+
{(field) => (
|
|
5326
|
+
<TextInput
|
|
5327
|
+
style={[
|
|
5328
|
+
styles.input,
|
|
5329
|
+
{
|
|
5330
|
+
color: theme.text,
|
|
5331
|
+
borderColor: theme.border,
|
|
5332
|
+
backgroundColor: theme.background,
|
|
5333
|
+
},
|
|
5334
|
+
]}
|
|
5335
|
+
placeholder="Password"
|
|
5336
|
+
placeholderTextColor={theme.text}
|
|
5337
|
+
value={field.state.value}
|
|
5338
|
+
onBlur={field.handleBlur}
|
|
5339
|
+
onChangeText={(value) => {
|
|
5340
|
+
field.handleChange(value);
|
|
5341
|
+
if (error) {
|
|
5342
|
+
setError(null);
|
|
5343
|
+
}
|
|
5344
|
+
}}
|
|
5345
|
+
secureTextEntry
|
|
5346
|
+
onSubmitEditing={form.handleSubmit}
|
|
5347
|
+
/>
|
|
5348
|
+
)}
|
|
5349
|
+
</form.Field>
|
|
5350
|
+
|
|
5351
|
+
<TouchableOpacity
|
|
5352
|
+
onPress={form.handleSubmit}
|
|
5353
|
+
disabled={isSubmitting}
|
|
5354
|
+
style={[
|
|
5355
|
+
styles.button,
|
|
5356
|
+
{
|
|
5357
|
+
backgroundColor: theme.primary,
|
|
5358
|
+
opacity: isSubmitting ? 0.5 : 1,
|
|
5359
|
+
},
|
|
5360
|
+
]}
|
|
5361
|
+
>
|
|
5362
|
+
{isSubmitting ? (
|
|
5363
|
+
<ActivityIndicator size="small" color="#ffffff" />
|
|
5364
|
+
) : (
|
|
5365
|
+
<Text style={styles.buttonText}>Sign In</Text>
|
|
5366
|
+
)}
|
|
5367
|
+
</TouchableOpacity>
|
|
5368
|
+
</>
|
|
5369
|
+
);
|
|
5370
|
+
}}
|
|
5371
|
+
</form.Subscribe>
|
|
5227
5372
|
</View>
|
|
5228
5373
|
);
|
|
5229
5374
|
}
|
|
@@ -5264,105 +5409,221 @@ const styles = StyleSheet.create({
|
|
|
5264
5409
|
});
|
|
5265
5410
|
|
|
5266
5411
|
export { SignIn };
|
|
5267
|
-
|
|
5268
5412
|
`],
|
|
5269
5413
|
["auth/better-auth/convex/native/bare/components/sign-up.tsx.hbs", `import { authClient } from "@/lib/auth-client";
|
|
5414
|
+
import { useForm } from "@tanstack/react-form";
|
|
5270
5415
|
import { useState } from "react";
|
|
5271
5416
|
import {
|
|
5272
5417
|
ActivityIndicator,
|
|
5418
|
+
StyleSheet,
|
|
5273
5419
|
Text,
|
|
5274
5420
|
TextInput,
|
|
5275
5421
|
TouchableOpacity,
|
|
5276
5422
|
View,
|
|
5277
|
-
StyleSheet,
|
|
5278
5423
|
} from "react-native";
|
|
5279
|
-
import { useColorScheme } from "@/lib/use-color-scheme";
|
|
5280
5424
|
import { NAV_THEME } from "@/lib/constants";
|
|
5425
|
+
import { useColorScheme } from "@/lib/use-color-scheme";
|
|
5426
|
+
import z from "zod";
|
|
5427
|
+
|
|
5428
|
+
const signUpSchema = z.object({
|
|
5429
|
+
name: z
|
|
5430
|
+
.string()
|
|
5431
|
+
.trim()
|
|
5432
|
+
.min(1, "Name is required")
|
|
5433
|
+
.min(2, "Name must be at least 2 characters"),
|
|
5434
|
+
email: z
|
|
5435
|
+
.string()
|
|
5436
|
+
.trim()
|
|
5437
|
+
.min(1, "Email is required")
|
|
5438
|
+
.email("Enter a valid email address"),
|
|
5439
|
+
password: z
|
|
5440
|
+
.string()
|
|
5441
|
+
.min(1, "Password is required")
|
|
5442
|
+
.min(8, "Use at least 8 characters"),
|
|
5443
|
+
});
|
|
5444
|
+
|
|
5445
|
+
function getErrorMessage(error: unknown): string | null {
|
|
5446
|
+
if (!error) return null;
|
|
5447
|
+
|
|
5448
|
+
if (typeof error === "string") {
|
|
5449
|
+
return error;
|
|
5450
|
+
}
|
|
5451
|
+
|
|
5452
|
+
if (Array.isArray(error)) {
|
|
5453
|
+
for (const issue of error) {
|
|
5454
|
+
const message = getErrorMessage(issue);
|
|
5455
|
+
if (message) {
|
|
5456
|
+
return message;
|
|
5457
|
+
}
|
|
5458
|
+
}
|
|
5459
|
+
return null;
|
|
5460
|
+
}
|
|
5461
|
+
|
|
5462
|
+
if (typeof error === "object" && error !== null) {
|
|
5463
|
+
const maybeError = error as { message?: unknown };
|
|
5464
|
+
if (typeof maybeError.message === "string") {
|
|
5465
|
+
return maybeError.message;
|
|
5466
|
+
}
|
|
5467
|
+
}
|
|
5468
|
+
|
|
5469
|
+
return null;
|
|
5470
|
+
}
|
|
5281
5471
|
|
|
5282
5472
|
function SignUp() {
|
|
5283
5473
|
const { colorScheme } = useColorScheme();
|
|
5284
5474
|
const theme = colorScheme === "dark" ? NAV_THEME.dark : NAV_THEME.light;
|
|
5285
|
-
const [name, setName] = useState("");
|
|
5286
|
-
const [email, setEmail] = useState("");
|
|
5287
|
-
const [password, setPassword] = useState("");
|
|
5288
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
5289
5475
|
const [error, setError] = useState<string | null>(null);
|
|
5290
5476
|
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
onSuccess() {
|
|
5307
|
-
setName("");
|
|
5308
|
-
setEmail("");
|
|
5309
|
-
setPassword("");
|
|
5477
|
+
const form = useForm({
|
|
5478
|
+
defaultValues: {
|
|
5479
|
+
name: "",
|
|
5480
|
+
email: "",
|
|
5481
|
+
password: "",
|
|
5482
|
+
},
|
|
5483
|
+
validators: {
|
|
5484
|
+
onSubmit: signUpSchema,
|
|
5485
|
+
},
|
|
5486
|
+
onSubmit: async ({ value, formApi }) => {
|
|
5487
|
+
await authClient.signUp.email(
|
|
5488
|
+
{
|
|
5489
|
+
name: value.name.trim(),
|
|
5490
|
+
email: value.email.trim(),
|
|
5491
|
+
password: value.password,
|
|
5310
5492
|
},
|
|
5311
|
-
|
|
5312
|
-
|
|
5493
|
+
{
|
|
5494
|
+
onError(error) {
|
|
5495
|
+
setError(error.error?.message || "Failed to sign up");
|
|
5496
|
+
},
|
|
5497
|
+
onSuccess() {
|
|
5498
|
+
setError(null);
|
|
5499
|
+
formApi.reset();
|
|
5500
|
+
},
|
|
5313
5501
|
},
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
}
|
|
5502
|
+
);
|
|
5503
|
+
},
|
|
5504
|
+
});
|
|
5317
5505
|
|
|
5318
5506
|
return (
|
|
5319
5507
|
<View style={[styles.card, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
|
5320
5508
|
<Text style={[styles.title, { color: theme.text }]}>Create Account</Text>
|
|
5321
5509
|
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5510
|
+
<form.Subscribe
|
|
5511
|
+
selector={(state) => ({
|
|
5512
|
+
isSubmitting: state.isSubmitting,
|
|
5513
|
+
validationError: getErrorMessage(state.errorMap.onSubmit),
|
|
5514
|
+
})}
|
|
5515
|
+
>
|
|
5516
|
+
{({ isSubmitting, validationError }) => {
|
|
5517
|
+
const formError = error ?? validationError;
|
|
5327
5518
|
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5519
|
+
return (
|
|
5520
|
+
<>
|
|
5521
|
+
{formError ? (
|
|
5522
|
+
<View style={[styles.errorContainer, { backgroundColor: theme.notification + "20" }]}>
|
|
5523
|
+
<Text style={[styles.errorText, { color: theme.notification }]}>{formError}</Text>
|
|
5524
|
+
</View>
|
|
5525
|
+
) : null}
|
|
5335
5526
|
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5527
|
+
<form.Field name="name">
|
|
5528
|
+
{(field) => (
|
|
5529
|
+
<TextInput
|
|
5530
|
+
style={[
|
|
5531
|
+
styles.input,
|
|
5532
|
+
{
|
|
5533
|
+
color: theme.text,
|
|
5534
|
+
borderColor: theme.border,
|
|
5535
|
+
backgroundColor: theme.background,
|
|
5536
|
+
},
|
|
5537
|
+
]}
|
|
5538
|
+
placeholder="Name"
|
|
5539
|
+
placeholderTextColor={theme.text}
|
|
5540
|
+
value={field.state.value}
|
|
5541
|
+
onBlur={field.handleBlur}
|
|
5542
|
+
onChangeText={(value) => {
|
|
5543
|
+
field.handleChange(value);
|
|
5544
|
+
if (error) {
|
|
5545
|
+
setError(null);
|
|
5546
|
+
}
|
|
5547
|
+
}}
|
|
5548
|
+
/>
|
|
5549
|
+
)}
|
|
5550
|
+
</form.Field>
|
|
5345
5551
|
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5552
|
+
<form.Field name="email">
|
|
5553
|
+
{(field) => (
|
|
5554
|
+
<TextInput
|
|
5555
|
+
style={[
|
|
5556
|
+
styles.input,
|
|
5557
|
+
{
|
|
5558
|
+
color: theme.text,
|
|
5559
|
+
borderColor: theme.border,
|
|
5560
|
+
backgroundColor: theme.background,
|
|
5561
|
+
},
|
|
5562
|
+
]}
|
|
5563
|
+
placeholder="Email"
|
|
5564
|
+
placeholderTextColor={theme.text}
|
|
5565
|
+
value={field.state.value}
|
|
5566
|
+
onBlur={field.handleBlur}
|
|
5567
|
+
onChangeText={(value) => {
|
|
5568
|
+
field.handleChange(value);
|
|
5569
|
+
if (error) {
|
|
5570
|
+
setError(null);
|
|
5571
|
+
}
|
|
5572
|
+
}}
|
|
5573
|
+
keyboardType="email-address"
|
|
5574
|
+
autoCapitalize="none"
|
|
5575
|
+
/>
|
|
5576
|
+
)}
|
|
5577
|
+
</form.Field>
|
|
5354
5578
|
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5579
|
+
<form.Field name="password">
|
|
5580
|
+
{(field) => (
|
|
5581
|
+
<TextInput
|
|
5582
|
+
style={[
|
|
5583
|
+
styles.input,
|
|
5584
|
+
{
|
|
5585
|
+
color: theme.text,
|
|
5586
|
+
borderColor: theme.border,
|
|
5587
|
+
backgroundColor: theme.background,
|
|
5588
|
+
},
|
|
5589
|
+
]}
|
|
5590
|
+
placeholder="Password"
|
|
5591
|
+
placeholderTextColor={theme.text}
|
|
5592
|
+
value={field.state.value}
|
|
5593
|
+
onBlur={field.handleBlur}
|
|
5594
|
+
onChangeText={(value) => {
|
|
5595
|
+
field.handleChange(value);
|
|
5596
|
+
if (error) {
|
|
5597
|
+
setError(null);
|
|
5598
|
+
}
|
|
5599
|
+
}}
|
|
5600
|
+
secureTextEntry
|
|
5601
|
+
onSubmitEditing={form.handleSubmit}
|
|
5602
|
+
/>
|
|
5603
|
+
)}
|
|
5604
|
+
</form.Field>
|
|
5605
|
+
|
|
5606
|
+
<TouchableOpacity
|
|
5607
|
+
onPress={form.handleSubmit}
|
|
5608
|
+
disabled={isSubmitting}
|
|
5609
|
+
style={[
|
|
5610
|
+
styles.button,
|
|
5611
|
+
{
|
|
5612
|
+
backgroundColor: theme.primary,
|
|
5613
|
+
opacity: isSubmitting ? 0.5 : 1,
|
|
5614
|
+
},
|
|
5615
|
+
]}
|
|
5616
|
+
>
|
|
5617
|
+
{isSubmitting ? (
|
|
5618
|
+
<ActivityIndicator size="small" color="#ffffff" />
|
|
5619
|
+
) : (
|
|
5620
|
+
<Text style={styles.buttonText}>Sign Up</Text>
|
|
5621
|
+
)}
|
|
5622
|
+
</TouchableOpacity>
|
|
5623
|
+
</>
|
|
5624
|
+
);
|
|
5625
|
+
}}
|
|
5626
|
+
</form.Subscribe>
|
|
5366
5627
|
</View>
|
|
5367
5628
|
);
|
|
5368
5629
|
}
|
|
@@ -5403,7 +5664,6 @@ const styles = StyleSheet.create({
|
|
|
5403
5664
|
});
|
|
5404
5665
|
|
|
5405
5666
|
export { SignUp };
|
|
5406
|
-
|
|
5407
5667
|
`],
|
|
5408
5668
|
["auth/better-auth/convex/native/base/lib/auth-client.ts.hbs", `import { createAuthClient } from "better-auth/react";
|
|
5409
5669
|
import { convexClient } from "@convex-dev/better-auth/client/plugins";
|
|
@@ -5425,6 +5685,7 @@ export const authClient = createAuthClient({
|
|
|
5425
5685
|
});
|
|
5426
5686
|
`],
|
|
5427
5687
|
["auth/better-auth/convex/native/unistyles/components/sign-in.tsx.hbs", `import { authClient } from "@/lib/auth-client";
|
|
5688
|
+
import { useForm } from "@tanstack/react-form";
|
|
5428
5689
|
import { useState } from "react";
|
|
5429
5690
|
import {
|
|
5430
5691
|
ActivityIndicator,
|
|
@@ -5434,76 +5695,151 @@ import {
|
|
|
5434
5695
|
View,
|
|
5435
5696
|
} from "react-native";
|
|
5436
5697
|
import { StyleSheet } from "react-native-unistyles";
|
|
5698
|
+
import z from "zod";
|
|
5437
5699
|
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5700
|
+
const signInSchema = z.object({
|
|
5701
|
+
email: z
|
|
5702
|
+
.string()
|
|
5703
|
+
.trim()
|
|
5704
|
+
.min(1, "Email is required")
|
|
5705
|
+
.email("Enter a valid email address"),
|
|
5706
|
+
password: z
|
|
5707
|
+
.string()
|
|
5708
|
+
.min(1, "Password is required")
|
|
5709
|
+
.min(8, "Use at least 8 characters"),
|
|
5710
|
+
});
|
|
5443
5711
|
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
setError(null);
|
|
5712
|
+
function getErrorMessage(error: unknown): string | null {
|
|
5713
|
+
if (!error) return null;
|
|
5447
5714
|
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5715
|
+
if (typeof error === "string") {
|
|
5716
|
+
return error;
|
|
5717
|
+
}
|
|
5718
|
+
|
|
5719
|
+
if (Array.isArray(error)) {
|
|
5720
|
+
for (const issue of error) {
|
|
5721
|
+
const message = getErrorMessage(issue);
|
|
5722
|
+
if (message) {
|
|
5723
|
+
return message;
|
|
5724
|
+
}
|
|
5725
|
+
}
|
|
5726
|
+
return null;
|
|
5727
|
+
}
|
|
5728
|
+
|
|
5729
|
+
if (typeof error === "object" && error !== null) {
|
|
5730
|
+
const maybeError = error as { message?: unknown };
|
|
5731
|
+
if (typeof maybeError.message === "string") {
|
|
5732
|
+
return maybeError.message;
|
|
5733
|
+
}
|
|
5734
|
+
}
|
|
5735
|
+
|
|
5736
|
+
return null;
|
|
5737
|
+
}
|
|
5738
|
+
|
|
5739
|
+
export function SignIn() {
|
|
5740
|
+
const [error, setError] = useState<string | null>(null);
|
|
5741
|
+
|
|
5742
|
+
const form = useForm({
|
|
5743
|
+
defaultValues: {
|
|
5744
|
+
email: "",
|
|
5745
|
+
password: "",
|
|
5746
|
+
},
|
|
5747
|
+
validators: {
|
|
5748
|
+
onSubmit: signInSchema,
|
|
5749
|
+
},
|
|
5750
|
+
onSubmit: async ({ value, formApi }) => {
|
|
5751
|
+
await authClient.signIn.email(
|
|
5752
|
+
{
|
|
5753
|
+
email: value.email.trim(),
|
|
5754
|
+
password: value.password,
|
|
5461
5755
|
},
|
|
5462
|
-
|
|
5463
|
-
|
|
5756
|
+
{
|
|
5757
|
+
onError(error) {
|
|
5758
|
+
setError(error.error?.message || "Failed to sign in");
|
|
5759
|
+
},
|
|
5760
|
+
onSuccess() {
|
|
5761
|
+
setError(null);
|
|
5762
|
+
formApi.reset();
|
|
5763
|
+
},
|
|
5464
5764
|
},
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
};
|
|
5765
|
+
);
|
|
5766
|
+
},
|
|
5767
|
+
});
|
|
5468
5768
|
|
|
5469
5769
|
return (
|
|
5470
5770
|
<View style={styles.container}>
|
|
5471
5771
|
<Text style={styles.title}>Sign In</Text>
|
|
5472
5772
|
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
placeholder="Email"
|
|
5482
|
-
value={email}
|
|
5483
|
-
onChangeText={setEmail}
|
|
5484
|
-
keyboardType="email-address"
|
|
5485
|
-
autoCapitalize="none"
|
|
5486
|
-
/>
|
|
5773
|
+
<form.Subscribe
|
|
5774
|
+
selector={(state) => ({
|
|
5775
|
+
isSubmitting: state.isSubmitting,
|
|
5776
|
+
validationError: getErrorMessage(state.errorMap.onSubmit),
|
|
5777
|
+
})}
|
|
5778
|
+
>
|
|
5779
|
+
{({ isSubmitting, validationError }) => {
|
|
5780
|
+
const formError = error ?? validationError;
|
|
5487
5781
|
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5782
|
+
return (
|
|
5783
|
+
<>
|
|
5784
|
+
{formError ? (
|
|
5785
|
+
<View style={styles.errorContainer}>
|
|
5786
|
+
<Text style={styles.errorText}>{formError}</Text>
|
|
5787
|
+
</View>
|
|
5788
|
+
) : null}
|
|
5789
|
+
|
|
5790
|
+
<form.Field name="email">
|
|
5791
|
+
{(field) => (
|
|
5792
|
+
<TextInput
|
|
5793
|
+
style={styles.input}
|
|
5794
|
+
placeholder="Email"
|
|
5795
|
+
value={field.state.value}
|
|
5796
|
+
onBlur={field.handleBlur}
|
|
5797
|
+
onChangeText={(value) => {
|
|
5798
|
+
field.handleChange(value);
|
|
5799
|
+
if (error) {
|
|
5800
|
+
setError(null);
|
|
5801
|
+
}
|
|
5802
|
+
}}
|
|
5803
|
+
keyboardType="email-address"
|
|
5804
|
+
autoCapitalize="none"
|
|
5805
|
+
/>
|
|
5806
|
+
)}
|
|
5807
|
+
</form.Field>
|
|
5808
|
+
|
|
5809
|
+
<form.Field name="password">
|
|
5810
|
+
{(field) => (
|
|
5811
|
+
<TextInput
|
|
5812
|
+
style={styles.input}
|
|
5813
|
+
placeholder="Password"
|
|
5814
|
+
value={field.state.value}
|
|
5815
|
+
onBlur={field.handleBlur}
|
|
5816
|
+
onChangeText={(value) => {
|
|
5817
|
+
field.handleChange(value);
|
|
5818
|
+
if (error) {
|
|
5819
|
+
setError(null);
|
|
5820
|
+
}
|
|
5821
|
+
}}
|
|
5822
|
+
secureTextEntry
|
|
5823
|
+
onSubmitEditing={form.handleSubmit}
|
|
5824
|
+
/>
|
|
5825
|
+
)}
|
|
5826
|
+
</form.Field>
|
|
5495
5827
|
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5828
|
+
<TouchableOpacity
|
|
5829
|
+
onPress={form.handleSubmit}
|
|
5830
|
+
disabled={isSubmitting}
|
|
5831
|
+
style={styles.button}
|
|
5832
|
+
>
|
|
5833
|
+
{isSubmitting ? (
|
|
5834
|
+
<ActivityIndicator size="small" color="#fff" />
|
|
5835
|
+
) : (
|
|
5836
|
+
<Text style={styles.buttonText}>Sign In</Text>
|
|
5837
|
+
)}
|
|
5838
|
+
</TouchableOpacity>
|
|
5839
|
+
</>
|
|
5840
|
+
);
|
|
5841
|
+
}}
|
|
5842
|
+
</form.Subscribe>
|
|
5507
5843
|
</View>
|
|
5508
5844
|
);
|
|
5509
5845
|
}
|
|
@@ -5553,6 +5889,7 @@ const styles = StyleSheet.create((theme) => ({
|
|
|
5553
5889
|
}));
|
|
5554
5890
|
`],
|
|
5555
5891
|
["auth/better-auth/convex/native/unistyles/components/sign-up.tsx.hbs", `import { authClient } from "@/lib/auth-client";
|
|
5892
|
+
import { useForm } from "@tanstack/react-form";
|
|
5556
5893
|
import { useState } from "react";
|
|
5557
5894
|
import {
|
|
5558
5895
|
ActivityIndicator,
|
|
@@ -5562,86 +5899,175 @@ import {
|
|
|
5562
5899
|
View,
|
|
5563
5900
|
} from "react-native";
|
|
5564
5901
|
import { StyleSheet } from "react-native-unistyles";
|
|
5902
|
+
import z from "zod";
|
|
5903
|
+
|
|
5904
|
+
const signUpSchema = z.object({
|
|
5905
|
+
name: z
|
|
5906
|
+
.string()
|
|
5907
|
+
.trim()
|
|
5908
|
+
.min(1, "Name is required")
|
|
5909
|
+
.min(2, "Name must be at least 2 characters"),
|
|
5910
|
+
email: z
|
|
5911
|
+
.string()
|
|
5912
|
+
.trim()
|
|
5913
|
+
.min(1, "Email is required")
|
|
5914
|
+
.email("Enter a valid email address"),
|
|
5915
|
+
password: z
|
|
5916
|
+
.string()
|
|
5917
|
+
.min(1, "Password is required")
|
|
5918
|
+
.min(8, "Use at least 8 characters"),
|
|
5919
|
+
});
|
|
5920
|
+
|
|
5921
|
+
function getErrorMessage(error: unknown): string | null {
|
|
5922
|
+
if (!error) return null;
|
|
5923
|
+
|
|
5924
|
+
if (typeof error === "string") {
|
|
5925
|
+
return error;
|
|
5926
|
+
}
|
|
5927
|
+
|
|
5928
|
+
if (Array.isArray(error)) {
|
|
5929
|
+
for (const issue of error) {
|
|
5930
|
+
const message = getErrorMessage(issue);
|
|
5931
|
+
if (message) {
|
|
5932
|
+
return message;
|
|
5933
|
+
}
|
|
5934
|
+
}
|
|
5935
|
+
return null;
|
|
5936
|
+
}
|
|
5937
|
+
|
|
5938
|
+
if (typeof error === "object" && error !== null) {
|
|
5939
|
+
const maybeError = error as { message?: unknown };
|
|
5940
|
+
if (typeof maybeError.message === "string") {
|
|
5941
|
+
return maybeError.message;
|
|
5942
|
+
}
|
|
5943
|
+
}
|
|
5944
|
+
|
|
5945
|
+
return null;
|
|
5946
|
+
}
|
|
5565
5947
|
|
|
5566
5948
|
export function SignUp() {
|
|
5567
|
-
const [name, setName] = useState("");
|
|
5568
|
-
const [email, setEmail] = useState("");
|
|
5569
|
-
const [password, setPassword] = useState("");
|
|
5570
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
5571
5949
|
const [error, setError] = useState<string | null>(null);
|
|
5572
5950
|
|
|
5573
|
-
const
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
onSuccess: () => {
|
|
5589
|
-
setName("");
|
|
5590
|
-
setEmail("");
|
|
5591
|
-
setPassword("");
|
|
5951
|
+
const form = useForm({
|
|
5952
|
+
defaultValues: {
|
|
5953
|
+
name: "",
|
|
5954
|
+
email: "",
|
|
5955
|
+
password: "",
|
|
5956
|
+
},
|
|
5957
|
+
validators: {
|
|
5958
|
+
onSubmit: signUpSchema,
|
|
5959
|
+
},
|
|
5960
|
+
onSubmit: async ({ value, formApi }) => {
|
|
5961
|
+
await authClient.signUp.email(
|
|
5962
|
+
{
|
|
5963
|
+
name: value.name.trim(),
|
|
5964
|
+
email: value.email.trim(),
|
|
5965
|
+
password: value.password,
|
|
5592
5966
|
},
|
|
5593
|
-
|
|
5594
|
-
|
|
5967
|
+
{
|
|
5968
|
+
onError(error) {
|
|
5969
|
+
setError(error.error?.message || "Failed to sign up");
|
|
5970
|
+
},
|
|
5971
|
+
onSuccess() {
|
|
5972
|
+
setError(null);
|
|
5973
|
+
formApi.reset();
|
|
5974
|
+
},
|
|
5595
5975
|
},
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
};
|
|
5976
|
+
);
|
|
5977
|
+
},
|
|
5978
|
+
});
|
|
5599
5979
|
|
|
5600
5980
|
return (
|
|
5601
5981
|
<View style={styles.container}>
|
|
5602
5982
|
<Text style={styles.title}>Create Account</Text>
|
|
5603
5983
|
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
placeholder="Name"
|
|
5613
|
-
value={name}
|
|
5614
|
-
onChangeText={setName}
|
|
5615
|
-
/>
|
|
5616
|
-
|
|
5617
|
-
<TextInput
|
|
5618
|
-
style={styles.input}
|
|
5619
|
-
placeholder="Email"
|
|
5620
|
-
value={email}
|
|
5621
|
-
onChangeText={setEmail}
|
|
5622
|
-
keyboardType="email-address"
|
|
5623
|
-
autoCapitalize="none"
|
|
5624
|
-
/>
|
|
5984
|
+
<form.Subscribe
|
|
5985
|
+
selector={(state) => ({
|
|
5986
|
+
isSubmitting: state.isSubmitting,
|
|
5987
|
+
validationError: getErrorMessage(state.errorMap.onSubmit),
|
|
5988
|
+
})}
|
|
5989
|
+
>
|
|
5990
|
+
{({ isSubmitting, validationError }) => {
|
|
5991
|
+
const formError = error ?? validationError;
|
|
5625
5992
|
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5993
|
+
return (
|
|
5994
|
+
<>
|
|
5995
|
+
{formError ? (
|
|
5996
|
+
<View style={styles.errorContainer}>
|
|
5997
|
+
<Text style={styles.errorText}>{formError}</Text>
|
|
5998
|
+
</View>
|
|
5999
|
+
) : null}
|
|
6000
|
+
|
|
6001
|
+
<form.Field name="name">
|
|
6002
|
+
{(field) => (
|
|
6003
|
+
<TextInput
|
|
6004
|
+
style={styles.input}
|
|
6005
|
+
placeholder="Name"
|
|
6006
|
+
value={field.state.value}
|
|
6007
|
+
onBlur={field.handleBlur}
|
|
6008
|
+
onChangeText={(value) => {
|
|
6009
|
+
field.handleChange(value);
|
|
6010
|
+
if (error) {
|
|
6011
|
+
setError(null);
|
|
6012
|
+
}
|
|
6013
|
+
}}
|
|
6014
|
+
/>
|
|
6015
|
+
)}
|
|
6016
|
+
</form.Field>
|
|
6017
|
+
|
|
6018
|
+
<form.Field name="email">
|
|
6019
|
+
{(field) => (
|
|
6020
|
+
<TextInput
|
|
6021
|
+
style={styles.input}
|
|
6022
|
+
placeholder="Email"
|
|
6023
|
+
value={field.state.value}
|
|
6024
|
+
onBlur={field.handleBlur}
|
|
6025
|
+
onChangeText={(value) => {
|
|
6026
|
+
field.handleChange(value);
|
|
6027
|
+
if (error) {
|
|
6028
|
+
setError(null);
|
|
6029
|
+
}
|
|
6030
|
+
}}
|
|
6031
|
+
keyboardType="email-address"
|
|
6032
|
+
autoCapitalize="none"
|
|
6033
|
+
/>
|
|
6034
|
+
)}
|
|
6035
|
+
</form.Field>
|
|
6036
|
+
|
|
6037
|
+
<form.Field name="password">
|
|
6038
|
+
{(field) => (
|
|
6039
|
+
<TextInput
|
|
6040
|
+
style={styles.inputLast}
|
|
6041
|
+
placeholder="Password"
|
|
6042
|
+
value={field.state.value}
|
|
6043
|
+
onBlur={field.handleBlur}
|
|
6044
|
+
onChangeText={(value) => {
|
|
6045
|
+
field.handleChange(value);
|
|
6046
|
+
if (error) {
|
|
6047
|
+
setError(null);
|
|
6048
|
+
}
|
|
6049
|
+
}}
|
|
6050
|
+
secureTextEntry
|
|
6051
|
+
onSubmitEditing={form.handleSubmit}
|
|
6052
|
+
/>
|
|
6053
|
+
)}
|
|
6054
|
+
</form.Field>
|
|
5633
6055
|
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
6056
|
+
<TouchableOpacity
|
|
6057
|
+
onPress={form.handleSubmit}
|
|
6058
|
+
disabled={isSubmitting}
|
|
6059
|
+
style={styles.button}
|
|
6060
|
+
>
|
|
6061
|
+
{isSubmitting ? (
|
|
6062
|
+
<ActivityIndicator size="small" color="#fff" />
|
|
6063
|
+
) : (
|
|
6064
|
+
<Text style={styles.buttonText}>Sign Up</Text>
|
|
6065
|
+
)}
|
|
6066
|
+
</TouchableOpacity>
|
|
6067
|
+
</>
|
|
6068
|
+
);
|
|
6069
|
+
}}
|
|
6070
|
+
</form.Subscribe>
|
|
5645
6071
|
</View>
|
|
5646
6072
|
);
|
|
5647
6073
|
}
|
|
@@ -5699,161 +6125,351 @@ const styles = StyleSheet.create((theme) => ({
|
|
|
5699
6125
|
}));
|
|
5700
6126
|
`],
|
|
5701
6127
|
["auth/better-auth/convex/native/uniwind/components/sign-in.tsx.hbs", `import { authClient } from "@/lib/auth-client";
|
|
5702
|
-
import {
|
|
5703
|
-
import {
|
|
5704
|
-
import {
|
|
6128
|
+
import { useForm } from "@tanstack/react-form";
|
|
6129
|
+
import { useRef } from "react";
|
|
6130
|
+
import { Text, TextInput, View } from "react-native";
|
|
6131
|
+
import { Button, FieldError, Input, Label, Spinner, Surface, TextField, useToast } from "heroui-native";
|
|
6132
|
+
import z from "zod";
|
|
5705
6133
|
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
6134
|
+
const signInSchema = z.object({
|
|
6135
|
+
email: z
|
|
6136
|
+
.string()
|
|
6137
|
+
.trim()
|
|
6138
|
+
.min(1, "Email is required")
|
|
6139
|
+
.email("Enter a valid email address"),
|
|
6140
|
+
password: z
|
|
6141
|
+
.string()
|
|
6142
|
+
.min(1, "Password is required")
|
|
6143
|
+
.min(8, "Use at least 8 characters"),
|
|
6144
|
+
});
|
|
5711
6145
|
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
setError(null);
|
|
6146
|
+
function getErrorMessage(error: unknown): string | null {
|
|
6147
|
+
if (!error) return null;
|
|
5715
6148
|
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
6149
|
+
if (typeof error === "string") {
|
|
6150
|
+
return error;
|
|
6151
|
+
}
|
|
6152
|
+
|
|
6153
|
+
if (Array.isArray(error)) {
|
|
6154
|
+
for (const issue of error) {
|
|
6155
|
+
const message = getErrorMessage(issue);
|
|
6156
|
+
if (message) {
|
|
6157
|
+
return message;
|
|
6158
|
+
}
|
|
6159
|
+
}
|
|
6160
|
+
return null;
|
|
6161
|
+
}
|
|
6162
|
+
|
|
6163
|
+
if (typeof error === "object" && error !== null) {
|
|
6164
|
+
const maybeError = error as { message?: unknown };
|
|
6165
|
+
if (typeof maybeError.message === "string") {
|
|
6166
|
+
return maybeError.message;
|
|
6167
|
+
}
|
|
6168
|
+
}
|
|
6169
|
+
|
|
6170
|
+
return null;
|
|
6171
|
+
}
|
|
6172
|
+
|
|
6173
|
+
export function SignIn() {
|
|
6174
|
+
const passwordInputRef = useRef<TextInput>(null);
|
|
6175
|
+
const { toast } = useToast();
|
|
6176
|
+
|
|
6177
|
+
const form = useForm({
|
|
6178
|
+
defaultValues: {
|
|
6179
|
+
email: "",
|
|
6180
|
+
password: "",
|
|
6181
|
+
},
|
|
6182
|
+
validators: {
|
|
6183
|
+
onSubmit: signInSchema,
|
|
6184
|
+
},
|
|
6185
|
+
onSubmit: async ({ value, formApi }) => {
|
|
6186
|
+
await authClient.signIn.email(
|
|
6187
|
+
{
|
|
6188
|
+
email: value.email.trim(),
|
|
6189
|
+
password: value.password,
|
|
5729
6190
|
},
|
|
5730
|
-
|
|
5731
|
-
|
|
6191
|
+
{
|
|
6192
|
+
onError(error) {
|
|
6193
|
+
toast.show({
|
|
6194
|
+
variant: "danger",
|
|
6195
|
+
label: error.error?.message || "Failed to sign in",
|
|
6196
|
+
});
|
|
6197
|
+
},
|
|
6198
|
+
onSuccess() {
|
|
6199
|
+
formApi.reset();
|
|
6200
|
+
toast.show({
|
|
6201
|
+
variant: "success",
|
|
6202
|
+
label: "Signed in successfully",
|
|
6203
|
+
});
|
|
6204
|
+
},
|
|
5732
6205
|
},
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
};
|
|
6206
|
+
);
|
|
6207
|
+
},
|
|
6208
|
+
});
|
|
5736
6209
|
|
|
5737
6210
|
return (
|
|
5738
6211
|
<Surface variant="secondary" className="p-4 rounded-lg">
|
|
5739
6212
|
<Text className="text-foreground font-medium mb-4">Sign In</Text>
|
|
5740
6213
|
|
|
5741
|
-
<
|
|
5742
|
-
{
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
value={email}
|
|
5750
|
-
onChangeText={setEmail}
|
|
5751
|
-
placeholder="email@example.com"
|
|
5752
|
-
keyboardType="email-address"
|
|
5753
|
-
autoCapitalize="none"
|
|
5754
|
-
/>
|
|
5755
|
-
</TextField>
|
|
5756
|
-
|
|
5757
|
-
<TextField>
|
|
5758
|
-
<TextField.Label>Password</TextField.Label>
|
|
5759
|
-
<TextField.Input
|
|
5760
|
-
value={password}
|
|
5761
|
-
onChangeText={setPassword}
|
|
5762
|
-
placeholder="••••••••"
|
|
5763
|
-
secureTextEntry
|
|
5764
|
-
/>
|
|
5765
|
-
</TextField>
|
|
6214
|
+
<form.Subscribe
|
|
6215
|
+
selector={(state) => ({
|
|
6216
|
+
isSubmitting: state.isSubmitting,
|
|
6217
|
+
validationError: getErrorMessage(state.errorMap.onSubmit),
|
|
6218
|
+
})}
|
|
6219
|
+
>
|
|
6220
|
+
{({ isSubmitting, validationError }) => {
|
|
6221
|
+
const formError = validationError;
|
|
5766
6222
|
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
6223
|
+
return (
|
|
6224
|
+
<>
|
|
6225
|
+
<FieldError isInvalid={!!formError} className="mb-3">
|
|
6226
|
+
{formError}
|
|
6227
|
+
</FieldError>
|
|
6228
|
+
|
|
6229
|
+
<View className="gap-3">
|
|
6230
|
+
<form.Field name="email">
|
|
6231
|
+
{(field) => (
|
|
6232
|
+
<TextField>
|
|
6233
|
+
<Label>Email</Label>
|
|
6234
|
+
<Input
|
|
6235
|
+
value={field.state.value}
|
|
6236
|
+
onBlur={field.handleBlur}
|
|
6237
|
+
onChangeText={field.handleChange}
|
|
6238
|
+
placeholder="email@example.com"
|
|
6239
|
+
keyboardType="email-address"
|
|
6240
|
+
autoCapitalize="none"
|
|
6241
|
+
autoComplete="email"
|
|
6242
|
+
textContentType="emailAddress"
|
|
6243
|
+
returnKeyType="next"
|
|
6244
|
+
blurOnSubmit={false}
|
|
6245
|
+
onSubmitEditing={() => {
|
|
6246
|
+
passwordInputRef.current?.focus();
|
|
6247
|
+
}}
|
|
6248
|
+
/>
|
|
6249
|
+
</TextField>
|
|
6250
|
+
)}
|
|
6251
|
+
</form.Field>
|
|
6252
|
+
|
|
6253
|
+
<form.Field name="password">
|
|
6254
|
+
{(field) => (
|
|
6255
|
+
<TextField>
|
|
6256
|
+
<Label>Password</Label>
|
|
6257
|
+
<Input
|
|
6258
|
+
ref={passwordInputRef}
|
|
6259
|
+
value={field.state.value}
|
|
6260
|
+
onBlur={field.handleBlur}
|
|
6261
|
+
onChangeText={field.handleChange}
|
|
6262
|
+
placeholder="••••••••"
|
|
6263
|
+
secureTextEntry
|
|
6264
|
+
autoComplete="password"
|
|
6265
|
+
textContentType="password"
|
|
6266
|
+
returnKeyType="go"
|
|
6267
|
+
onSubmitEditing={form.handleSubmit}
|
|
6268
|
+
/>
|
|
6269
|
+
</TextField>
|
|
6270
|
+
)}
|
|
6271
|
+
</form.Field>
|
|
6272
|
+
|
|
6273
|
+
<Button onPress={form.handleSubmit} isDisabled={isSubmitting} className="mt-1">
|
|
6274
|
+
{isSubmitting ? <Spinner size="sm" color="default" /> : <Button.Label>Sign In</Button.Label>}
|
|
6275
|
+
</Button>
|
|
6276
|
+
</View>
|
|
6277
|
+
</>
|
|
6278
|
+
);
|
|
6279
|
+
}}
|
|
6280
|
+
</form.Subscribe>
|
|
5771
6281
|
</Surface>
|
|
5772
6282
|
);
|
|
5773
6283
|
}
|
|
5774
6284
|
`],
|
|
5775
6285
|
["auth/better-auth/convex/native/uniwind/components/sign-up.tsx.hbs", `import { authClient } from "@/lib/auth-client";
|
|
5776
|
-
import {
|
|
5777
|
-
import {
|
|
5778
|
-
import {
|
|
6286
|
+
import { useForm } from "@tanstack/react-form";
|
|
6287
|
+
import { useRef } from "react";
|
|
6288
|
+
import { Text, TextInput, View } from "react-native";
|
|
6289
|
+
import { Button, FieldError, Input, Label, Spinner, Surface, TextField, useToast } from "heroui-native";
|
|
6290
|
+
import z from "zod";
|
|
5779
6291
|
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
6292
|
+
const signUpSchema = z.object({
|
|
6293
|
+
name: z
|
|
6294
|
+
.string()
|
|
6295
|
+
.trim()
|
|
6296
|
+
.min(1, "Name is required")
|
|
6297
|
+
.min(2, "Name must be at least 2 characters"),
|
|
6298
|
+
email: z
|
|
6299
|
+
.string()
|
|
6300
|
+
.trim()
|
|
6301
|
+
.min(1, "Email is required")
|
|
6302
|
+
.email("Enter a valid email address"),
|
|
6303
|
+
password: z
|
|
6304
|
+
.string()
|
|
6305
|
+
.min(1, "Password is required")
|
|
6306
|
+
.min(8, "Use at least 8 characters"),
|
|
6307
|
+
});
|
|
5786
6308
|
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
setError(null);
|
|
6309
|
+
function getErrorMessage(error: unknown): string | null {
|
|
6310
|
+
if (!error) return null;
|
|
5790
6311
|
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
6312
|
+
if (typeof error === "string") {
|
|
6313
|
+
return error;
|
|
6314
|
+
}
|
|
6315
|
+
|
|
6316
|
+
if (Array.isArray(error)) {
|
|
6317
|
+
for (const issue of error) {
|
|
6318
|
+
const message = getErrorMessage(issue);
|
|
6319
|
+
if (message) {
|
|
6320
|
+
return message;
|
|
6321
|
+
}
|
|
6322
|
+
}
|
|
6323
|
+
return null;
|
|
6324
|
+
}
|
|
6325
|
+
|
|
6326
|
+
if (typeof error === "object" && error !== null) {
|
|
6327
|
+
const maybeError = error as { message?: unknown };
|
|
6328
|
+
if (typeof maybeError.message === "string") {
|
|
6329
|
+
return maybeError.message;
|
|
6330
|
+
}
|
|
6331
|
+
}
|
|
6332
|
+
|
|
6333
|
+
return null;
|
|
6334
|
+
}
|
|
6335
|
+
|
|
6336
|
+
export function SignUp() {
|
|
6337
|
+
const emailInputRef = useRef<TextInput>(null);
|
|
6338
|
+
const passwordInputRef = useRef<TextInput>(null);
|
|
6339
|
+
const { toast } = useToast();
|
|
6340
|
+
|
|
6341
|
+
const form = useForm({
|
|
6342
|
+
defaultValues: {
|
|
6343
|
+
name: "",
|
|
6344
|
+
email: "",
|
|
6345
|
+
password: "",
|
|
6346
|
+
},
|
|
6347
|
+
validators: {
|
|
6348
|
+
onSubmit: signUpSchema,
|
|
6349
|
+
},
|
|
6350
|
+
onSubmit: async ({ value, formApi }) => {
|
|
6351
|
+
await authClient.signUp.email(
|
|
6352
|
+
{
|
|
6353
|
+
name: value.name.trim(),
|
|
6354
|
+
email: value.email.trim(),
|
|
6355
|
+
password: value.password,
|
|
5806
6356
|
},
|
|
5807
|
-
|
|
5808
|
-
|
|
6357
|
+
{
|
|
6358
|
+
onError(error) {
|
|
6359
|
+
toast.show({
|
|
6360
|
+
variant: "danger",
|
|
6361
|
+
label: error.error?.message || "Failed to sign up",
|
|
6362
|
+
});
|
|
6363
|
+
},
|
|
6364
|
+
onSuccess() {
|
|
6365
|
+
formApi.reset();
|
|
6366
|
+
toast.show({
|
|
6367
|
+
variant: "success",
|
|
6368
|
+
label: "Account created successfully",
|
|
6369
|
+
});
|
|
6370
|
+
},
|
|
5809
6371
|
},
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
};
|
|
6372
|
+
);
|
|
6373
|
+
},
|
|
6374
|
+
});
|
|
5813
6375
|
|
|
5814
6376
|
return (
|
|
5815
6377
|
<Surface variant="secondary" className="p-4 rounded-lg">
|
|
5816
6378
|
<Text className="text-foreground font-medium mb-4">Create Account</Text>
|
|
5817
6379
|
|
|
5818
|
-
<
|
|
5819
|
-
{
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
</TextField>
|
|
5827
|
-
|
|
5828
|
-
<TextField>
|
|
5829
|
-
<TextField.Label>Email</TextField.Label>
|
|
5830
|
-
<TextField.Input
|
|
5831
|
-
value={email}
|
|
5832
|
-
onChangeText={setEmail}
|
|
5833
|
-
placeholder="email@example.com"
|
|
5834
|
-
keyboardType="email-address"
|
|
5835
|
-
autoCapitalize="none"
|
|
5836
|
-
/>
|
|
5837
|
-
</TextField>
|
|
5838
|
-
|
|
5839
|
-
<TextField>
|
|
5840
|
-
<TextField.Label>Password</TextField.Label>
|
|
5841
|
-
<TextField.Input
|
|
5842
|
-
value={password}
|
|
5843
|
-
onChangeText={setPassword}
|
|
5844
|
-
placeholder="••••••••"
|
|
5845
|
-
secureTextEntry
|
|
5846
|
-
/>
|
|
5847
|
-
</TextField>
|
|
6380
|
+
<form.Subscribe
|
|
6381
|
+
selector={(state) => ({
|
|
6382
|
+
isSubmitting: state.isSubmitting,
|
|
6383
|
+
validationError: getErrorMessage(state.errorMap.onSubmit),
|
|
6384
|
+
})}
|
|
6385
|
+
>
|
|
6386
|
+
{({ isSubmitting, validationError }) => {
|
|
6387
|
+
const formError = validationError;
|
|
5848
6388
|
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
6389
|
+
return (
|
|
6390
|
+
<>
|
|
6391
|
+
<FieldError isInvalid={!!formError} className="mb-3">
|
|
6392
|
+
{formError}
|
|
6393
|
+
</FieldError>
|
|
6394
|
+
|
|
6395
|
+
<View className="gap-3">
|
|
6396
|
+
<form.Field name="name">
|
|
6397
|
+
{(field) => (
|
|
6398
|
+
<TextField>
|
|
6399
|
+
<Label>Name</Label>
|
|
6400
|
+
<Input
|
|
6401
|
+
value={field.state.value}
|
|
6402
|
+
onBlur={field.handleBlur}
|
|
6403
|
+
onChangeText={field.handleChange}
|
|
6404
|
+
placeholder="John Doe"
|
|
6405
|
+
autoComplete="name"
|
|
6406
|
+
textContentType="name"
|
|
6407
|
+
returnKeyType="next"
|
|
6408
|
+
blurOnSubmit={false}
|
|
6409
|
+
onSubmitEditing={() => {
|
|
6410
|
+
emailInputRef.current?.focus();
|
|
6411
|
+
}}
|
|
6412
|
+
/>
|
|
6413
|
+
</TextField>
|
|
6414
|
+
)}
|
|
6415
|
+
</form.Field>
|
|
6416
|
+
|
|
6417
|
+
<form.Field name="email">
|
|
6418
|
+
{(field) => (
|
|
6419
|
+
<TextField>
|
|
6420
|
+
<Label>Email</Label>
|
|
6421
|
+
<Input
|
|
6422
|
+
ref={emailInputRef}
|
|
6423
|
+
value={field.state.value}
|
|
6424
|
+
onBlur={field.handleBlur}
|
|
6425
|
+
onChangeText={field.handleChange}
|
|
6426
|
+
placeholder="email@example.com"
|
|
6427
|
+
keyboardType="email-address"
|
|
6428
|
+
autoCapitalize="none"
|
|
6429
|
+
autoComplete="email"
|
|
6430
|
+
textContentType="emailAddress"
|
|
6431
|
+
returnKeyType="next"
|
|
6432
|
+
blurOnSubmit={false}
|
|
6433
|
+
onSubmitEditing={() => {
|
|
6434
|
+
passwordInputRef.current?.focus();
|
|
6435
|
+
}}
|
|
6436
|
+
/>
|
|
6437
|
+
</TextField>
|
|
6438
|
+
)}
|
|
6439
|
+
</form.Field>
|
|
6440
|
+
|
|
6441
|
+
<form.Field name="password">
|
|
6442
|
+
{(field) => (
|
|
6443
|
+
<TextField>
|
|
6444
|
+
<Label>Password</Label>
|
|
6445
|
+
<Input
|
|
6446
|
+
ref={passwordInputRef}
|
|
6447
|
+
value={field.state.value}
|
|
6448
|
+
onBlur={field.handleBlur}
|
|
6449
|
+
onChangeText={field.handleChange}
|
|
6450
|
+
placeholder="••••••••"
|
|
6451
|
+
secureTextEntry
|
|
6452
|
+
autoComplete="new-password"
|
|
6453
|
+
textContentType="newPassword"
|
|
6454
|
+
returnKeyType="go"
|
|
6455
|
+
onSubmitEditing={form.handleSubmit}
|
|
6456
|
+
/>
|
|
6457
|
+
</TextField>
|
|
6458
|
+
)}
|
|
6459
|
+
</form.Field>
|
|
6460
|
+
|
|
6461
|
+
<Button onPress={form.handleSubmit} isDisabled={isSubmitting} className="mt-1">
|
|
6462
|
+
{isSubmitting ? (
|
|
6463
|
+
<Spinner size="sm" color="default" />
|
|
6464
|
+
) : (
|
|
6465
|
+
<Button.Label>Create Account</Button.Label>
|
|
6466
|
+
)}
|
|
6467
|
+
</Button>
|
|
6468
|
+
</View>
|
|
6469
|
+
</>
|
|
6470
|
+
);
|
|
6471
|
+
}}
|
|
6472
|
+
</form.Subscribe>
|
|
5857
6473
|
</Surface>
|
|
5858
6474
|
);
|
|
5859
6475
|
}
|
|
@@ -7333,130 +7949,234 @@ import { queryClient } from "@/utils/trpc";
|
|
|
7333
7949
|
{{#if (eq api "orpc")}}
|
|
7334
7950
|
import { queryClient } from "@/utils/orpc";
|
|
7335
7951
|
{{/if}}
|
|
7952
|
+
import { useForm } from "@tanstack/react-form";
|
|
7336
7953
|
import { useState } from "react";
|
|
7337
7954
|
import {
|
|
7338
|
-
ActivityIndicator,
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7955
|
+
ActivityIndicator,
|
|
7956
|
+
StyleSheet,
|
|
7957
|
+
Text,
|
|
7958
|
+
TextInput,
|
|
7959
|
+
TouchableOpacity,
|
|
7960
|
+
View,
|
|
7344
7961
|
} from "react-native";
|
|
7345
|
-
import { useColorScheme } from "@/lib/use-color-scheme";
|
|
7346
7962
|
import { NAV_THEME } from "@/lib/constants";
|
|
7963
|
+
import { useColorScheme } from "@/lib/use-color-scheme";
|
|
7964
|
+
import z from "zod";
|
|
7347
7965
|
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
|
|
7966
|
+
const signInSchema = z.object({
|
|
7967
|
+
email: z
|
|
7968
|
+
.string()
|
|
7969
|
+
.trim()
|
|
7970
|
+
.min(1, "Email is required")
|
|
7971
|
+
.email("Enter a valid email address"),
|
|
7972
|
+
password: z
|
|
7973
|
+
.string()
|
|
7974
|
+
.min(1, "Password is required")
|
|
7975
|
+
.min(8, "Use at least 8 characters"),
|
|
7976
|
+
});
|
|
7977
|
+
|
|
7978
|
+
function getErrorMessage(error: unknown): string | null {
|
|
7979
|
+
if (!error) return null;
|
|
7354
7980
|
|
|
7355
|
-
|
|
7356
|
-
|
|
7981
|
+
if (typeof error === "string") {
|
|
7982
|
+
return error;
|
|
7983
|
+
}
|
|
7984
|
+
|
|
7985
|
+
if (Array.isArray(error)) {
|
|
7986
|
+
for (const issue of error) {
|
|
7987
|
+
const message = getErrorMessage(issue);
|
|
7988
|
+
if (message) {
|
|
7989
|
+
return message;
|
|
7990
|
+
}
|
|
7357
7991
|
}
|
|
7992
|
+
return null;
|
|
7993
|
+
}
|
|
7358
7994
|
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
|
|
7995
|
+
if (typeof error === "object" && error !== null) {
|
|
7996
|
+
const maybeError = error as { message?: unknown };
|
|
7997
|
+
if (typeof maybeError.message === "string") {
|
|
7998
|
+
return maybeError.message;
|
|
7999
|
+
}
|
|
8000
|
+
}
|
|
7362
8001
|
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
|
|
7367
|
-
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
8002
|
+
return null;
|
|
8003
|
+
}
|
|
8004
|
+
|
|
8005
|
+
function SignIn() {
|
|
8006
|
+
const { colorScheme } = useColorScheme();
|
|
8007
|
+
const theme = colorScheme === "dark" ? NAV_THEME.dark : NAV_THEME.light;
|
|
8008
|
+
const [error, setError] = useState<string | null>(null);
|
|
8009
|
+
|
|
8010
|
+
const form = useForm({
|
|
8011
|
+
defaultValues: {
|
|
8012
|
+
email: "",
|
|
8013
|
+
password: "",
|
|
7372
8014
|
},
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
{{#if (eq api "orpc")}}
|
|
7376
|
-
queryClient.refetchQueries();
|
|
7377
|
-
{{/if}}
|
|
7378
|
-
{{#if (eq api "trpc")}}
|
|
7379
|
-
queryClient.refetchQueries();
|
|
7380
|
-
{{/if}}
|
|
8015
|
+
validators: {
|
|
8016
|
+
onSubmit: signInSchema,
|
|
7381
8017
|
},
|
|
7382
|
-
|
|
7383
|
-
|
|
8018
|
+
onSubmit: async ({ value, formApi }) => {
|
|
8019
|
+
await authClient.signIn.email(
|
|
8020
|
+
{
|
|
8021
|
+
email: value.email.trim(),
|
|
8022
|
+
password: value.password,
|
|
8023
|
+
},
|
|
8024
|
+
{
|
|
8025
|
+
onError(error) {
|
|
8026
|
+
setError(error.error?.message || "Failed to sign in");
|
|
8027
|
+
},
|
|
8028
|
+
onSuccess() {
|
|
8029
|
+
setError(null);
|
|
8030
|
+
formApi.reset();
|
|
8031
|
+
{{#if (eq api "orpc")}}
|
|
8032
|
+
queryClient.refetchQueries();
|
|
8033
|
+
{{/if}}
|
|
8034
|
+
{{#if (eq api "trpc")}}
|
|
8035
|
+
queryClient.refetchQueries();
|
|
8036
|
+
{{/if}}
|
|
8037
|
+
},
|
|
8038
|
+
},
|
|
8039
|
+
);
|
|
7384
8040
|
},
|
|
7385
|
-
|
|
7386
|
-
);
|
|
7387
|
-
}
|
|
8041
|
+
});
|
|
7388
8042
|
|
|
7389
|
-
|
|
8043
|
+
return (
|
|
7390
8044
|
<View style={[styles.card, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
|
7391
|
-
|
|
8045
|
+
<Text style={[styles.title, { color: theme.text }]}>Sign In</Text>
|
|
7392
8046
|
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
)
|
|
8047
|
+
<form.Subscribe
|
|
8048
|
+
selector={(state) => ({
|
|
8049
|
+
isSubmitting: state.isSubmitting,
|
|
8050
|
+
validationError: getErrorMessage(state.errorMap.onSubmit),
|
|
8051
|
+
})}
|
|
8052
|
+
>
|
|
8053
|
+
{({ isSubmitting, validationError }) => {
|
|
8054
|
+
const formError = error ?? validationError;
|
|
7398
8055
|
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
8056
|
+
return (
|
|
8057
|
+
<>
|
|
8058
|
+
{formError ? (
|
|
8059
|
+
<View style={[styles.errorContainer, { backgroundColor: theme.notification + "20" }]}>
|
|
8060
|
+
<Text style={[styles.errorText, { color: theme.notification }]}>{formError}</Text>
|
|
8061
|
+
</View>
|
|
8062
|
+
) : null}
|
|
7405
8063
|
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
|
|
7410
|
-
|
|
8064
|
+
<form.Field name="email">
|
|
8065
|
+
{(field) => (
|
|
8066
|
+
<TextInput
|
|
8067
|
+
style={[
|
|
8068
|
+
styles.input,
|
|
8069
|
+
{
|
|
8070
|
+
color: theme.text,
|
|
8071
|
+
borderColor: theme.border,
|
|
8072
|
+
backgroundColor: theme.background,
|
|
8073
|
+
},
|
|
8074
|
+
]}
|
|
8075
|
+
placeholder="Email"
|
|
8076
|
+
placeholderTextColor={theme.text}
|
|
8077
|
+
value={field.state.value}
|
|
8078
|
+
onBlur={field.handleBlur}
|
|
8079
|
+
onChangeText={(value) => {
|
|
8080
|
+
field.handleChange(value);
|
|
8081
|
+
if (error) {
|
|
8082
|
+
setError(null);
|
|
8083
|
+
}
|
|
8084
|
+
}}
|
|
8085
|
+
keyboardType="email-address"
|
|
8086
|
+
autoCapitalize="none"
|
|
8087
|
+
/>
|
|
8088
|
+
)}
|
|
8089
|
+
</form.Field>
|
|
7411
8090
|
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
|
|
8091
|
+
<form.Field name="password">
|
|
8092
|
+
{(field) => (
|
|
8093
|
+
<TextInput
|
|
8094
|
+
style={[
|
|
8095
|
+
styles.input,
|
|
8096
|
+
{
|
|
8097
|
+
color: theme.text,
|
|
8098
|
+
borderColor: theme.border,
|
|
8099
|
+
backgroundColor: theme.background,
|
|
8100
|
+
},
|
|
8101
|
+
]}
|
|
8102
|
+
placeholder="Password"
|
|
8103
|
+
placeholderTextColor={theme.text}
|
|
8104
|
+
value={field.state.value}
|
|
8105
|
+
onBlur={field.handleBlur}
|
|
8106
|
+
onChangeText={(value) => {
|
|
8107
|
+
field.handleChange(value);
|
|
8108
|
+
if (error) {
|
|
8109
|
+
setError(null);
|
|
8110
|
+
}
|
|
8111
|
+
}}
|
|
8112
|
+
secureTextEntry
|
|
8113
|
+
onSubmitEditing={form.handleSubmit}
|
|
8114
|
+
/>
|
|
8115
|
+
)}
|
|
8116
|
+
</form.Field>
|
|
8117
|
+
|
|
8118
|
+
<TouchableOpacity
|
|
8119
|
+
onPress={form.handleSubmit}
|
|
8120
|
+
disabled={isSubmitting}
|
|
8121
|
+
style={[
|
|
8122
|
+
styles.button,
|
|
8123
|
+
{
|
|
8124
|
+
backgroundColor: theme.primary,
|
|
8125
|
+
opacity: isSubmitting ? 0.5 : 1,
|
|
8126
|
+
},
|
|
8127
|
+
]}
|
|
8128
|
+
>
|
|
8129
|
+
{isSubmitting ? (
|
|
8130
|
+
<ActivityIndicator size="small" color="#ffffff" />
|
|
8131
|
+
) : (
|
|
8132
|
+
<Text style={styles.buttonText}>Sign In</Text>
|
|
8133
|
+
)}
|
|
8134
|
+
</TouchableOpacity>
|
|
8135
|
+
</>
|
|
8136
|
+
);
|
|
8137
|
+
}}
|
|
8138
|
+
</form.Subscribe>
|
|
7420
8139
|
</View>
|
|
7421
|
-
|
|
7422
|
-
|
|
8140
|
+
);
|
|
8141
|
+
}
|
|
7423
8142
|
|
|
7424
|
-
|
|
7425
|
-
|
|
8143
|
+
const styles = StyleSheet.create({
|
|
8144
|
+
card: {
|
|
7426
8145
|
marginTop: 16,
|
|
7427
8146
|
padding: 16,
|
|
7428
8147
|
borderWidth: 1,
|
|
7429
|
-
|
|
7430
|
-
|
|
8148
|
+
},
|
|
8149
|
+
title: {
|
|
7431
8150
|
fontSize: 18,
|
|
7432
8151
|
fontWeight: "bold",
|
|
7433
8152
|
marginBottom: 12,
|
|
7434
|
-
|
|
7435
|
-
|
|
8153
|
+
},
|
|
8154
|
+
errorContainer: {
|
|
7436
8155
|
marginBottom: 12,
|
|
7437
8156
|
padding: 8,
|
|
7438
|
-
|
|
7439
|
-
|
|
8157
|
+
},
|
|
8158
|
+
errorText: {
|
|
7440
8159
|
fontSize: 14,
|
|
7441
|
-
|
|
7442
|
-
|
|
8160
|
+
},
|
|
8161
|
+
input: {
|
|
7443
8162
|
borderWidth: 1,
|
|
7444
8163
|
padding: 12,
|
|
7445
8164
|
fontSize: 16,
|
|
7446
|
-
marginBottom: 12,
|
|
7447
|
-
|
|
7448
|
-
|
|
8165
|
+
marginBottom: 12,
|
|
8166
|
+
},
|
|
8167
|
+
button: {
|
|
7449
8168
|
padding: 12,
|
|
7450
8169
|
alignItems: "center",
|
|
7451
8170
|
justifyContent: "center",
|
|
7452
|
-
|
|
7453
|
-
|
|
8171
|
+
},
|
|
8172
|
+
buttonText: {
|
|
7454
8173
|
color: "#ffffff",
|
|
7455
8174
|
fontSize: 16,
|
|
7456
|
-
|
|
7457
|
-
|
|
8175
|
+
},
|
|
8176
|
+
});
|
|
7458
8177
|
|
|
7459
|
-
|
|
8178
|
+
export { SignIn };
|
|
8179
|
+
`],
|
|
7460
8180
|
["auth/better-auth/native/bare/components/sign-up.tsx.hbs", `import { authClient } from "@/lib/auth-client";
|
|
7461
8181
|
{{#if (eq api "trpc")}}
|
|
7462
8182
|
import { queryClient } from "@/utils/trpc";
|
|
@@ -7464,108 +8184,225 @@ import { queryClient } from "@/utils/trpc";
|
|
|
7464
8184
|
{{#if (eq api "orpc")}}
|
|
7465
8185
|
import { queryClient } from "@/utils/orpc";
|
|
7466
8186
|
{{/if}}
|
|
8187
|
+
import { useForm } from "@tanstack/react-form";
|
|
7467
8188
|
import { useState } from "react";
|
|
7468
8189
|
import {
|
|
7469
8190
|
ActivityIndicator,
|
|
8191
|
+
StyleSheet,
|
|
7470
8192
|
Text,
|
|
7471
8193
|
TextInput,
|
|
7472
8194
|
TouchableOpacity,
|
|
7473
8195
|
View,
|
|
7474
|
-
StyleSheet,
|
|
7475
8196
|
} from "react-native";
|
|
7476
|
-
import { useColorScheme } from "@/lib/use-color-scheme";
|
|
7477
8197
|
import { NAV_THEME } from "@/lib/constants";
|
|
8198
|
+
import { useColorScheme } from "@/lib/use-color-scheme";
|
|
8199
|
+
import z from "zod";
|
|
8200
|
+
|
|
8201
|
+
const signUpSchema = z.object({
|
|
8202
|
+
name: z
|
|
8203
|
+
.string()
|
|
8204
|
+
.trim()
|
|
8205
|
+
.min(1, "Name is required")
|
|
8206
|
+
.min(2, "Name must be at least 2 characters"),
|
|
8207
|
+
email: z
|
|
8208
|
+
.string()
|
|
8209
|
+
.trim()
|
|
8210
|
+
.min(1, "Email is required")
|
|
8211
|
+
.email("Enter a valid email address"),
|
|
8212
|
+
password: z
|
|
8213
|
+
.string()
|
|
8214
|
+
.min(1, "Password is required")
|
|
8215
|
+
.min(8, "Use at least 8 characters"),
|
|
8216
|
+
});
|
|
8217
|
+
|
|
8218
|
+
function getErrorMessage(error: unknown): string | null {
|
|
8219
|
+
if (!error) return null;
|
|
8220
|
+
|
|
8221
|
+
if (typeof error === "string") {
|
|
8222
|
+
return error;
|
|
8223
|
+
}
|
|
8224
|
+
|
|
8225
|
+
if (Array.isArray(error)) {
|
|
8226
|
+
for (const issue of error) {
|
|
8227
|
+
const message = getErrorMessage(issue);
|
|
8228
|
+
if (message) {
|
|
8229
|
+
return message;
|
|
8230
|
+
}
|
|
8231
|
+
}
|
|
8232
|
+
return null;
|
|
8233
|
+
}
|
|
8234
|
+
|
|
8235
|
+
if (typeof error === "object" && error !== null) {
|
|
8236
|
+
const maybeError = error as { message?: unknown };
|
|
8237
|
+
if (typeof maybeError.message === "string") {
|
|
8238
|
+
return maybeError.message;
|
|
8239
|
+
}
|
|
8240
|
+
}
|
|
8241
|
+
|
|
8242
|
+
return null;
|
|
8243
|
+
}
|
|
7478
8244
|
|
|
7479
8245
|
function SignUp() {
|
|
7480
8246
|
const { colorScheme } = useColorScheme();
|
|
7481
8247
|
const theme = colorScheme === "dark" ? NAV_THEME.dark : NAV_THEME.light;
|
|
7482
|
-
const [name, setName] = useState("");
|
|
7483
|
-
const [email, setEmail] = useState("");
|
|
7484
|
-
const [password, setPassword] = useState("");
|
|
7485
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
7486
8248
|
const [error, setError] = useState<string | null>(null);
|
|
7487
8249
|
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
onSuccess() {
|
|
7504
|
-
setName("");
|
|
7505
|
-
setEmail("");
|
|
7506
|
-
setPassword("");
|
|
7507
|
-
{{#if (eq api "orpc")}}
|
|
7508
|
-
queryClient.refetchQueries();
|
|
7509
|
-
{{/if}}
|
|
7510
|
-
{{#if (eq api "trpc")}}
|
|
7511
|
-
queryClient.refetchQueries();
|
|
7512
|
-
{{/if}}
|
|
8250
|
+
const form = useForm({
|
|
8251
|
+
defaultValues: {
|
|
8252
|
+
name: "",
|
|
8253
|
+
email: "",
|
|
8254
|
+
password: "",
|
|
8255
|
+
},
|
|
8256
|
+
validators: {
|
|
8257
|
+
onSubmit: signUpSchema,
|
|
8258
|
+
},
|
|
8259
|
+
onSubmit: async ({ value, formApi }) => {
|
|
8260
|
+
await authClient.signUp.email(
|
|
8261
|
+
{
|
|
8262
|
+
name: value.name.trim(),
|
|
8263
|
+
email: value.email.trim(),
|
|
8264
|
+
password: value.password,
|
|
7513
8265
|
},
|
|
7514
|
-
|
|
7515
|
-
|
|
8266
|
+
{
|
|
8267
|
+
onError(error) {
|
|
8268
|
+
setError(error.error?.message || "Failed to sign up");
|
|
8269
|
+
},
|
|
8270
|
+
onSuccess() {
|
|
8271
|
+
setError(null);
|
|
8272
|
+
formApi.reset();
|
|
8273
|
+
{{#if (eq api "orpc")}}
|
|
8274
|
+
queryClient.refetchQueries();
|
|
8275
|
+
{{/if}}
|
|
8276
|
+
{{#if (eq api "trpc")}}
|
|
8277
|
+
queryClient.refetchQueries();
|
|
8278
|
+
{{/if}}
|
|
8279
|
+
},
|
|
7516
8280
|
},
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
}
|
|
8281
|
+
);
|
|
8282
|
+
},
|
|
8283
|
+
});
|
|
7520
8284
|
|
|
7521
8285
|
return (
|
|
7522
8286
|
<View style={[styles.card, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
|
7523
8287
|
<Text style={[styles.title, { color: theme.text }]}>Create Account</Text>
|
|
7524
8288
|
|
|
7525
|
-
|
|
7526
|
-
|
|
7527
|
-
|
|
7528
|
-
|
|
7529
|
-
|
|
8289
|
+
<form.Subscribe
|
|
8290
|
+
selector={(state) => ({
|
|
8291
|
+
isSubmitting: state.isSubmitting,
|
|
8292
|
+
validationError: getErrorMessage(state.errorMap.onSubmit),
|
|
8293
|
+
})}
|
|
8294
|
+
>
|
|
8295
|
+
{({ isSubmitting, validationError }) => {
|
|
8296
|
+
const formError = error ?? validationError;
|
|
7530
8297
|
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
8298
|
+
return (
|
|
8299
|
+
<>
|
|
8300
|
+
{formError ? (
|
|
8301
|
+
<View style={[styles.errorContainer, { backgroundColor: theme.notification + "20" }]}>
|
|
8302
|
+
<Text style={[styles.errorText, { color: theme.notification }]}>{formError}</Text>
|
|
8303
|
+
</View>
|
|
8304
|
+
) : null}
|
|
7538
8305
|
|
|
7539
|
-
|
|
7540
|
-
|
|
7541
|
-
|
|
7542
|
-
|
|
7543
|
-
|
|
7544
|
-
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
|
|
8306
|
+
<form.Field name="name">
|
|
8307
|
+
{(field) => (
|
|
8308
|
+
<TextInput
|
|
8309
|
+
style={[
|
|
8310
|
+
styles.input,
|
|
8311
|
+
{
|
|
8312
|
+
color: theme.text,
|
|
8313
|
+
borderColor: theme.border,
|
|
8314
|
+
backgroundColor: theme.background,
|
|
8315
|
+
},
|
|
8316
|
+
]}
|
|
8317
|
+
placeholder="Name"
|
|
8318
|
+
placeholderTextColor={theme.text}
|
|
8319
|
+
value={field.state.value}
|
|
8320
|
+
onBlur={field.handleBlur}
|
|
8321
|
+
onChangeText={(value) => {
|
|
8322
|
+
field.handleChange(value);
|
|
8323
|
+
if (error) {
|
|
8324
|
+
setError(null);
|
|
8325
|
+
}
|
|
8326
|
+
}}
|
|
8327
|
+
/>
|
|
8328
|
+
)}
|
|
8329
|
+
</form.Field>
|
|
7548
8330
|
|
|
7549
|
-
|
|
7550
|
-
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
|
|
7555
|
-
|
|
7556
|
-
|
|
8331
|
+
<form.Field name="email">
|
|
8332
|
+
{(field) => (
|
|
8333
|
+
<TextInput
|
|
8334
|
+
style={[
|
|
8335
|
+
styles.input,
|
|
8336
|
+
{
|
|
8337
|
+
color: theme.text,
|
|
8338
|
+
borderColor: theme.border,
|
|
8339
|
+
backgroundColor: theme.background,
|
|
8340
|
+
},
|
|
8341
|
+
]}
|
|
8342
|
+
placeholder="Email"
|
|
8343
|
+
placeholderTextColor={theme.text}
|
|
8344
|
+
value={field.state.value}
|
|
8345
|
+
onBlur={field.handleBlur}
|
|
8346
|
+
onChangeText={(value) => {
|
|
8347
|
+
field.handleChange(value);
|
|
8348
|
+
if (error) {
|
|
8349
|
+
setError(null);
|
|
8350
|
+
}
|
|
8351
|
+
}}
|
|
8352
|
+
keyboardType="email-address"
|
|
8353
|
+
autoCapitalize="none"
|
|
8354
|
+
/>
|
|
8355
|
+
)}
|
|
8356
|
+
</form.Field>
|
|
7557
8357
|
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
|
|
7566
|
-
|
|
7567
|
-
|
|
7568
|
-
|
|
8358
|
+
<form.Field name="password">
|
|
8359
|
+
{(field) => (
|
|
8360
|
+
<TextInput
|
|
8361
|
+
style={[
|
|
8362
|
+
styles.input,
|
|
8363
|
+
{
|
|
8364
|
+
color: theme.text,
|
|
8365
|
+
borderColor: theme.border,
|
|
8366
|
+
backgroundColor: theme.background,
|
|
8367
|
+
},
|
|
8368
|
+
]}
|
|
8369
|
+
placeholder="Password"
|
|
8370
|
+
placeholderTextColor={theme.text}
|
|
8371
|
+
value={field.state.value}
|
|
8372
|
+
onBlur={field.handleBlur}
|
|
8373
|
+
onChangeText={(value) => {
|
|
8374
|
+
field.handleChange(value);
|
|
8375
|
+
if (error) {
|
|
8376
|
+
setError(null);
|
|
8377
|
+
}
|
|
8378
|
+
}}
|
|
8379
|
+
secureTextEntry
|
|
8380
|
+
onSubmitEditing={form.handleSubmit}
|
|
8381
|
+
/>
|
|
8382
|
+
)}
|
|
8383
|
+
</form.Field>
|
|
8384
|
+
|
|
8385
|
+
<TouchableOpacity
|
|
8386
|
+
onPress={form.handleSubmit}
|
|
8387
|
+
disabled={isSubmitting}
|
|
8388
|
+
style={[
|
|
8389
|
+
styles.button,
|
|
8390
|
+
{
|
|
8391
|
+
backgroundColor: theme.primary,
|
|
8392
|
+
opacity: isSubmitting ? 0.5 : 1,
|
|
8393
|
+
},
|
|
8394
|
+
]}
|
|
8395
|
+
>
|
|
8396
|
+
{isSubmitting ? (
|
|
8397
|
+
<ActivityIndicator size="small" color="#ffffff" />
|
|
8398
|
+
) : (
|
|
8399
|
+
<Text style={styles.buttonText}>Sign Up</Text>
|
|
8400
|
+
)}
|
|
8401
|
+
</TouchableOpacity>
|
|
8402
|
+
</>
|
|
8403
|
+
);
|
|
8404
|
+
}}
|
|
8405
|
+
</form.Subscribe>
|
|
7569
8406
|
</View>
|
|
7570
8407
|
);
|
|
7571
8408
|
}
|
|
@@ -7606,7 +8443,6 @@ const styles = StyleSheet.create({
|
|
|
7606
8443
|
});
|
|
7607
8444
|
|
|
7608
8445
|
export { SignUp };
|
|
7609
|
-
|
|
7610
8446
|
`],
|
|
7611
8447
|
["auth/better-auth/native/base/lib/auth-client.ts.hbs", `import { expoClient } from "@better-auth/expo/client";
|
|
7612
8448
|
import { createAuthClient } from "better-auth/react";
|
|
@@ -7820,6 +8656,7 @@ import { queryClient } from "@/utils/trpc";
|
|
|
7820
8656
|
{{#if (eq api "orpc")}}
|
|
7821
8657
|
import { queryClient } from "@/utils/orpc";
|
|
7822
8658
|
{{/if}}
|
|
8659
|
+
import { useForm } from "@tanstack/react-form";
|
|
7823
8660
|
import { useState } from "react";
|
|
7824
8661
|
import {
|
|
7825
8662
|
ActivityIndicator,
|
|
@@ -7829,82 +8666,157 @@ import {
|
|
|
7829
8666
|
View,
|
|
7830
8667
|
} from "react-native";
|
|
7831
8668
|
import { StyleSheet } from "react-native-unistyles";
|
|
8669
|
+
import z from "zod";
|
|
8670
|
+
|
|
8671
|
+
const signInSchema = z.object({
|
|
8672
|
+
email: z
|
|
8673
|
+
.string()
|
|
8674
|
+
.trim()
|
|
8675
|
+
.min(1, "Email is required")
|
|
8676
|
+
.email("Enter a valid email address"),
|
|
8677
|
+
password: z
|
|
8678
|
+
.string()
|
|
8679
|
+
.min(1, "Password is required")
|
|
8680
|
+
.min(8, "Use at least 8 characters"),
|
|
8681
|
+
});
|
|
8682
|
+
|
|
8683
|
+
function getErrorMessage(error: unknown): string | null {
|
|
8684
|
+
if (!error) return null;
|
|
8685
|
+
|
|
8686
|
+
if (typeof error === "string") {
|
|
8687
|
+
return error;
|
|
8688
|
+
}
|
|
8689
|
+
|
|
8690
|
+
if (Array.isArray(error)) {
|
|
8691
|
+
for (const issue of error) {
|
|
8692
|
+
const message = getErrorMessage(issue);
|
|
8693
|
+
if (message) {
|
|
8694
|
+
return message;
|
|
8695
|
+
}
|
|
8696
|
+
}
|
|
8697
|
+
return null;
|
|
8698
|
+
}
|
|
8699
|
+
|
|
8700
|
+
if (typeof error === "object" && error !== null) {
|
|
8701
|
+
const maybeError = error as { message?: unknown };
|
|
8702
|
+
if (typeof maybeError.message === "string") {
|
|
8703
|
+
return maybeError.message;
|
|
8704
|
+
}
|
|
8705
|
+
}
|
|
8706
|
+
|
|
8707
|
+
return null;
|
|
8708
|
+
}
|
|
7832
8709
|
|
|
7833
8710
|
export function SignIn() {
|
|
7834
|
-
const [email, setEmail] = useState("");
|
|
7835
|
-
const [password, setPassword] = useState("");
|
|
7836
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
7837
8711
|
const [error, setError] = useState<string | null>(null);
|
|
7838
8712
|
|
|
7839
|
-
const
|
|
7840
|
-
|
|
7841
|
-
|
|
7842
|
-
|
|
7843
|
-
|
|
7844
|
-
|
|
7845
|
-
|
|
7846
|
-
|
|
7847
|
-
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
|
|
7852
|
-
},
|
|
7853
|
-
onSuccess: () => {
|
|
7854
|
-
setEmail("");
|
|
7855
|
-
setPassword("");
|
|
7856
|
-
{{#if (eq api "orpc")}}
|
|
7857
|
-
queryClient.refetchQueries();
|
|
7858
|
-
{{/if}}
|
|
7859
|
-
{{#if (eq api "trpc")}}
|
|
7860
|
-
queryClient.refetchQueries();
|
|
7861
|
-
{{/if}}
|
|
8713
|
+
const form = useForm({
|
|
8714
|
+
defaultValues: {
|
|
8715
|
+
email: "",
|
|
8716
|
+
password: "",
|
|
8717
|
+
},
|
|
8718
|
+
validators: {
|
|
8719
|
+
onSubmit: signInSchema,
|
|
8720
|
+
},
|
|
8721
|
+
onSubmit: async ({ value, formApi }) => {
|
|
8722
|
+
await authClient.signIn.email(
|
|
8723
|
+
{
|
|
8724
|
+
email: value.email.trim(),
|
|
8725
|
+
password: value.password,
|
|
7862
8726
|
},
|
|
7863
|
-
|
|
7864
|
-
|
|
8727
|
+
{
|
|
8728
|
+
onError(error) {
|
|
8729
|
+
setError(error.error?.message || "Failed to sign in");
|
|
8730
|
+
},
|
|
8731
|
+
onSuccess() {
|
|
8732
|
+
setError(null);
|
|
8733
|
+
formApi.reset();
|
|
8734
|
+
{{#if (eq api "orpc")}}
|
|
8735
|
+
queryClient.refetchQueries();
|
|
8736
|
+
{{/if}}
|
|
8737
|
+
{{#if (eq api "trpc")}}
|
|
8738
|
+
queryClient.refetchQueries();
|
|
8739
|
+
{{/if}}
|
|
8740
|
+
},
|
|
7865
8741
|
},
|
|
7866
|
-
|
|
7867
|
-
|
|
7868
|
-
};
|
|
8742
|
+
);
|
|
8743
|
+
},
|
|
8744
|
+
});
|
|
7869
8745
|
|
|
7870
8746
|
return (
|
|
7871
8747
|
<View style={styles.container}>
|
|
7872
8748
|
<Text style={styles.title}>Sign In</Text>
|
|
7873
8749
|
|
|
7874
|
-
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
placeholder="Email"
|
|
7883
|
-
value={email}
|
|
7884
|
-
onChangeText={setEmail}
|
|
7885
|
-
keyboardType="email-address"
|
|
7886
|
-
autoCapitalize="none"
|
|
7887
|
-
/>
|
|
8750
|
+
<form.Subscribe
|
|
8751
|
+
selector={(state) => ({
|
|
8752
|
+
isSubmitting: state.isSubmitting,
|
|
8753
|
+
validationError: getErrorMessage(state.errorMap.onSubmit),
|
|
8754
|
+
})}
|
|
8755
|
+
>
|
|
8756
|
+
{({ isSubmitting, validationError }) => {
|
|
8757
|
+
const formError = error ?? validationError;
|
|
7888
8758
|
|
|
7889
|
-
|
|
7890
|
-
|
|
7891
|
-
|
|
7892
|
-
|
|
7893
|
-
|
|
7894
|
-
|
|
7895
|
-
|
|
8759
|
+
return (
|
|
8760
|
+
<>
|
|
8761
|
+
{formError ? (
|
|
8762
|
+
<View style={styles.errorContainer}>
|
|
8763
|
+
<Text style={styles.errorText}>{formError}</Text>
|
|
8764
|
+
</View>
|
|
8765
|
+
) : null}
|
|
8766
|
+
|
|
8767
|
+
<form.Field name="email">
|
|
8768
|
+
{(field) => (
|
|
8769
|
+
<TextInput
|
|
8770
|
+
style={styles.input}
|
|
8771
|
+
placeholder="Email"
|
|
8772
|
+
value={field.state.value}
|
|
8773
|
+
onBlur={field.handleBlur}
|
|
8774
|
+
onChangeText={(value) => {
|
|
8775
|
+
field.handleChange(value);
|
|
8776
|
+
if (error) {
|
|
8777
|
+
setError(null);
|
|
8778
|
+
}
|
|
8779
|
+
}}
|
|
8780
|
+
keyboardType="email-address"
|
|
8781
|
+
autoCapitalize="none"
|
|
8782
|
+
/>
|
|
8783
|
+
)}
|
|
8784
|
+
</form.Field>
|
|
8785
|
+
|
|
8786
|
+
<form.Field name="password">
|
|
8787
|
+
{(field) => (
|
|
8788
|
+
<TextInput
|
|
8789
|
+
style={styles.input}
|
|
8790
|
+
placeholder="Password"
|
|
8791
|
+
value={field.state.value}
|
|
8792
|
+
onBlur={field.handleBlur}
|
|
8793
|
+
onChangeText={(value) => {
|
|
8794
|
+
field.handleChange(value);
|
|
8795
|
+
if (error) {
|
|
8796
|
+
setError(null);
|
|
8797
|
+
}
|
|
8798
|
+
}}
|
|
8799
|
+
secureTextEntry
|
|
8800
|
+
onSubmitEditing={form.handleSubmit}
|
|
8801
|
+
/>
|
|
8802
|
+
)}
|
|
8803
|
+
</form.Field>
|
|
7896
8804
|
|
|
7897
|
-
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
|
|
8805
|
+
<TouchableOpacity
|
|
8806
|
+
onPress={form.handleSubmit}
|
|
8807
|
+
disabled={isSubmitting}
|
|
8808
|
+
style={styles.button}
|
|
8809
|
+
>
|
|
8810
|
+
{isSubmitting ? (
|
|
8811
|
+
<ActivityIndicator size="small" color="#fff" />
|
|
8812
|
+
) : (
|
|
8813
|
+
<Text style={styles.buttonText}>Sign In</Text>
|
|
8814
|
+
)}
|
|
8815
|
+
</TouchableOpacity>
|
|
8816
|
+
</>
|
|
8817
|
+
);
|
|
8818
|
+
}}
|
|
8819
|
+
</form.Subscribe>
|
|
7908
8820
|
</View>
|
|
7909
8821
|
);
|
|
7910
8822
|
}
|
|
@@ -7960,6 +8872,7 @@ import { queryClient } from "@/utils/trpc";
|
|
|
7960
8872
|
{{#if (eq api "orpc")}}
|
|
7961
8873
|
import { queryClient } from "@/utils/orpc";
|
|
7962
8874
|
{{/if}}
|
|
8875
|
+
import { useForm } from "@tanstack/react-form";
|
|
7963
8876
|
import { useState } from "react";
|
|
7964
8877
|
import {
|
|
7965
8878
|
ActivityIndicator,
|
|
@@ -7969,92 +8882,181 @@ import {
|
|
|
7969
8882
|
View,
|
|
7970
8883
|
} from "react-native";
|
|
7971
8884
|
import { StyleSheet } from "react-native-unistyles";
|
|
8885
|
+
import z from "zod";
|
|
8886
|
+
|
|
8887
|
+
const signUpSchema = z.object({
|
|
8888
|
+
name: z
|
|
8889
|
+
.string()
|
|
8890
|
+
.trim()
|
|
8891
|
+
.min(1, "Name is required")
|
|
8892
|
+
.min(2, "Name must be at least 2 characters"),
|
|
8893
|
+
email: z
|
|
8894
|
+
.string()
|
|
8895
|
+
.trim()
|
|
8896
|
+
.min(1, "Email is required")
|
|
8897
|
+
.email("Enter a valid email address"),
|
|
8898
|
+
password: z
|
|
8899
|
+
.string()
|
|
8900
|
+
.min(1, "Password is required")
|
|
8901
|
+
.min(8, "Use at least 8 characters"),
|
|
8902
|
+
});
|
|
8903
|
+
|
|
8904
|
+
function getErrorMessage(error: unknown): string | null {
|
|
8905
|
+
if (!error) return null;
|
|
8906
|
+
|
|
8907
|
+
if (typeof error === "string") {
|
|
8908
|
+
return error;
|
|
8909
|
+
}
|
|
8910
|
+
|
|
8911
|
+
if (Array.isArray(error)) {
|
|
8912
|
+
for (const issue of error) {
|
|
8913
|
+
const message = getErrorMessage(issue);
|
|
8914
|
+
if (message) {
|
|
8915
|
+
return message;
|
|
8916
|
+
}
|
|
8917
|
+
}
|
|
8918
|
+
return null;
|
|
8919
|
+
}
|
|
8920
|
+
|
|
8921
|
+
if (typeof error === "object" && error !== null) {
|
|
8922
|
+
const maybeError = error as { message?: unknown };
|
|
8923
|
+
if (typeof maybeError.message === "string") {
|
|
8924
|
+
return maybeError.message;
|
|
8925
|
+
}
|
|
8926
|
+
}
|
|
8927
|
+
|
|
8928
|
+
return null;
|
|
8929
|
+
}
|
|
7972
8930
|
|
|
7973
8931
|
export function SignUp() {
|
|
7974
|
-
const [name, setName] = useState("");
|
|
7975
|
-
const [email, setEmail] = useState("");
|
|
7976
|
-
const [password, setPassword] = useState("");
|
|
7977
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
7978
8932
|
const [error, setError] = useState<string | null>(null);
|
|
7979
8933
|
|
|
7980
|
-
const
|
|
7981
|
-
|
|
7982
|
-
|
|
7983
|
-
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
|
|
7987
|
-
|
|
7988
|
-
|
|
7989
|
-
|
|
7990
|
-
|
|
7991
|
-
|
|
7992
|
-
|
|
7993
|
-
|
|
7994
|
-
|
|
7995
|
-
onSuccess: () => {
|
|
7996
|
-
setName("");
|
|
7997
|
-
setEmail("");
|
|
7998
|
-
setPassword("");
|
|
7999
|
-
{{#if (eq api "orpc")}}
|
|
8000
|
-
queryClient.refetchQueries();
|
|
8001
|
-
{{/if}}
|
|
8002
|
-
{{#if (eq api "trpc")}}
|
|
8003
|
-
queryClient.refetchQueries();
|
|
8004
|
-
{{/if}}
|
|
8005
|
-
},
|
|
8006
|
-
onFinished: () => {
|
|
8007
|
-
setIsLoading(false);
|
|
8934
|
+
const form = useForm({
|
|
8935
|
+
defaultValues: {
|
|
8936
|
+
name: "",
|
|
8937
|
+
email: "",
|
|
8938
|
+
password: "",
|
|
8939
|
+
},
|
|
8940
|
+
validators: {
|
|
8941
|
+
onSubmit: signUpSchema,
|
|
8942
|
+
},
|
|
8943
|
+
onSubmit: async ({ value, formApi }) => {
|
|
8944
|
+
await authClient.signUp.email(
|
|
8945
|
+
{
|
|
8946
|
+
name: value.name.trim(),
|
|
8947
|
+
email: value.email.trim(),
|
|
8948
|
+
password: value.password,
|
|
8008
8949
|
},
|
|
8009
|
-
|
|
8010
|
-
|
|
8011
|
-
|
|
8950
|
+
{
|
|
8951
|
+
onError(error) {
|
|
8952
|
+
setError(error.error?.message || "Failed to sign up");
|
|
8953
|
+
},
|
|
8954
|
+
onSuccess() {
|
|
8955
|
+
setError(null);
|
|
8956
|
+
formApi.reset();
|
|
8957
|
+
{{#if (eq api "orpc")}}
|
|
8958
|
+
queryClient.refetchQueries();
|
|
8959
|
+
{{/if}}
|
|
8960
|
+
{{#if (eq api "trpc")}}
|
|
8961
|
+
queryClient.refetchQueries();
|
|
8962
|
+
{{/if}}
|
|
8963
|
+
},
|
|
8964
|
+
},
|
|
8965
|
+
);
|
|
8966
|
+
},
|
|
8967
|
+
});
|
|
8012
8968
|
|
|
8013
8969
|
return (
|
|
8014
8970
|
<View style={styles.container}>
|
|
8015
8971
|
<Text style={styles.title}>Create Account</Text>
|
|
8016
8972
|
|
|
8017
|
-
|
|
8018
|
-
|
|
8019
|
-
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
placeholder="Name"
|
|
8026
|
-
value={name}
|
|
8027
|
-
onChangeText={setName}
|
|
8028
|
-
/>
|
|
8029
|
-
|
|
8030
|
-
<TextInput
|
|
8031
|
-
style={styles.input}
|
|
8032
|
-
placeholder="Email"
|
|
8033
|
-
value={email}
|
|
8034
|
-
onChangeText={setEmail}
|
|
8035
|
-
keyboardType="email-address"
|
|
8036
|
-
autoCapitalize="none"
|
|
8037
|
-
/>
|
|
8973
|
+
<form.Subscribe
|
|
8974
|
+
selector={(state) => ({
|
|
8975
|
+
isSubmitting: state.isSubmitting,
|
|
8976
|
+
validationError: getErrorMessage(state.errorMap.onSubmit),
|
|
8977
|
+
})}
|
|
8978
|
+
>
|
|
8979
|
+
{({ isSubmitting, validationError }) => {
|
|
8980
|
+
const formError = error ?? validationError;
|
|
8038
8981
|
|
|
8039
|
-
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
|
|
8043
|
-
|
|
8044
|
-
|
|
8045
|
-
|
|
8982
|
+
return (
|
|
8983
|
+
<>
|
|
8984
|
+
{formError ? (
|
|
8985
|
+
<View style={styles.errorContainer}>
|
|
8986
|
+
<Text style={styles.errorText}>{formError}</Text>
|
|
8987
|
+
</View>
|
|
8988
|
+
) : null}
|
|
8989
|
+
|
|
8990
|
+
<form.Field name="name">
|
|
8991
|
+
{(field) => (
|
|
8992
|
+
<TextInput
|
|
8993
|
+
style={styles.input}
|
|
8994
|
+
placeholder="Name"
|
|
8995
|
+
value={field.state.value}
|
|
8996
|
+
onBlur={field.handleBlur}
|
|
8997
|
+
onChangeText={(value) => {
|
|
8998
|
+
field.handleChange(value);
|
|
8999
|
+
if (error) {
|
|
9000
|
+
setError(null);
|
|
9001
|
+
}
|
|
9002
|
+
}}
|
|
9003
|
+
/>
|
|
9004
|
+
)}
|
|
9005
|
+
</form.Field>
|
|
9006
|
+
|
|
9007
|
+
<form.Field name="email">
|
|
9008
|
+
{(field) => (
|
|
9009
|
+
<TextInput
|
|
9010
|
+
style={styles.input}
|
|
9011
|
+
placeholder="Email"
|
|
9012
|
+
value={field.state.value}
|
|
9013
|
+
onBlur={field.handleBlur}
|
|
9014
|
+
onChangeText={(value) => {
|
|
9015
|
+
field.handleChange(value);
|
|
9016
|
+
if (error) {
|
|
9017
|
+
setError(null);
|
|
9018
|
+
}
|
|
9019
|
+
}}
|
|
9020
|
+
keyboardType="email-address"
|
|
9021
|
+
autoCapitalize="none"
|
|
9022
|
+
/>
|
|
9023
|
+
)}
|
|
9024
|
+
</form.Field>
|
|
9025
|
+
|
|
9026
|
+
<form.Field name="password">
|
|
9027
|
+
{(field) => (
|
|
9028
|
+
<TextInput
|
|
9029
|
+
style={styles.inputLast}
|
|
9030
|
+
placeholder="Password"
|
|
9031
|
+
value={field.state.value}
|
|
9032
|
+
onBlur={field.handleBlur}
|
|
9033
|
+
onChangeText={(value) => {
|
|
9034
|
+
field.handleChange(value);
|
|
9035
|
+
if (error) {
|
|
9036
|
+
setError(null);
|
|
9037
|
+
}
|
|
9038
|
+
}}
|
|
9039
|
+
secureTextEntry
|
|
9040
|
+
onSubmitEditing={form.handleSubmit}
|
|
9041
|
+
/>
|
|
9042
|
+
)}
|
|
9043
|
+
</form.Field>
|
|
8046
9044
|
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
|
|
9045
|
+
<TouchableOpacity
|
|
9046
|
+
onPress={form.handleSubmit}
|
|
9047
|
+
disabled={isSubmitting}
|
|
9048
|
+
style={styles.button}
|
|
9049
|
+
>
|
|
9050
|
+
{isSubmitting ? (
|
|
9051
|
+
<ActivityIndicator size="small" color="#fff" />
|
|
9052
|
+
) : (
|
|
9053
|
+
<Text style={styles.buttonText}>Sign Up</Text>
|
|
9054
|
+
)}
|
|
9055
|
+
</TouchableOpacity>
|
|
9056
|
+
</>
|
|
9057
|
+
);
|
|
9058
|
+
}}
|
|
9059
|
+
</form.Subscribe>
|
|
8058
9060
|
</View>
|
|
8059
9061
|
);
|
|
8060
9062
|
}
|
|
@@ -8241,86 +9243,171 @@ import { queryClient } from "@/utils/trpc";
|
|
|
8241
9243
|
{{#if (eq api "orpc")}}
|
|
8242
9244
|
import { queryClient } from "@/utils/orpc";
|
|
8243
9245
|
{{/if}}
|
|
8244
|
-
import {
|
|
8245
|
-
import {
|
|
8246
|
-
import {
|
|
9246
|
+
import { useForm } from "@tanstack/react-form";
|
|
9247
|
+
import { useRef } from "react";
|
|
9248
|
+
import { Text, TextInput, View } from "react-native";
|
|
9249
|
+
import { Button, FieldError, Input, Label, Spinner, Surface, TextField, useToast } from "heroui-native";
|
|
9250
|
+
import z from "zod";
|
|
8247
9251
|
|
|
8248
|
-
|
|
8249
|
-
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
|
|
9252
|
+
const signInSchema = z.object({
|
|
9253
|
+
email: z
|
|
9254
|
+
.string()
|
|
9255
|
+
.trim()
|
|
9256
|
+
.min(1, "Email is required")
|
|
9257
|
+
.email("Enter a valid email address"),
|
|
9258
|
+
password: z
|
|
9259
|
+
.string()
|
|
9260
|
+
.min(1, "Password is required")
|
|
9261
|
+
.min(8, "Use at least 8 characters"),
|
|
9262
|
+
});
|
|
8253
9263
|
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
setError(null);
|
|
9264
|
+
function getErrorMessage(error: unknown): string | null {
|
|
9265
|
+
if (!error) return null;
|
|
8257
9266
|
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
email,
|
|
8261
|
-
password,
|
|
8262
|
-
},
|
|
8263
|
-
{
|
|
8264
|
-
onError(error) {
|
|
8265
|
-
setError(error.error?.message || "Failed to sign in");
|
|
8266
|
-
setIsLoading(false);
|
|
8267
|
-
},
|
|
8268
|
-
onSuccess() {
|
|
8269
|
-
setEmail("");
|
|
8270
|
-
setPassword("");
|
|
8271
|
-
{{#if (eq api "orpc")}}
|
|
8272
|
-
queryClient.refetchQueries();
|
|
8273
|
-
{{/if}}
|
|
8274
|
-
{{#if (eq api "trpc")}}
|
|
8275
|
-
queryClient.refetchQueries();
|
|
8276
|
-
{{/if}}
|
|
8277
|
-
},
|
|
8278
|
-
onFinished() {
|
|
8279
|
-
setIsLoading(false);
|
|
8280
|
-
},
|
|
9267
|
+
if (typeof error === "string") {
|
|
9268
|
+
return error;
|
|
8281
9269
|
}
|
|
8282
|
-
|
|
9270
|
+
|
|
9271
|
+
if (Array.isArray(error)) {
|
|
9272
|
+
for (const issue of error) {
|
|
9273
|
+
const message = getErrorMessage(issue);
|
|
9274
|
+
if (message) {
|
|
9275
|
+
return message;
|
|
9276
|
+
}
|
|
9277
|
+
}
|
|
9278
|
+
return null;
|
|
9279
|
+
}
|
|
9280
|
+
|
|
9281
|
+
if (typeof error === "object" && error !== null) {
|
|
9282
|
+
const maybeError = error as { message?: unknown };
|
|
9283
|
+
if (typeof maybeError.message === "string") {
|
|
9284
|
+
return maybeError.message;
|
|
9285
|
+
}
|
|
8283
9286
|
}
|
|
8284
9287
|
|
|
9288
|
+
return null;
|
|
9289
|
+
}
|
|
9290
|
+
|
|
9291
|
+
function SignIn() {
|
|
9292
|
+
const passwordInputRef = useRef<TextInput>(null);
|
|
9293
|
+
const { toast } = useToast();
|
|
9294
|
+
|
|
9295
|
+
const form = useForm({
|
|
9296
|
+
defaultValues: {
|
|
9297
|
+
email: "",
|
|
9298
|
+
password: "",
|
|
9299
|
+
},
|
|
9300
|
+
validators: {
|
|
9301
|
+
onSubmit: signInSchema,
|
|
9302
|
+
},
|
|
9303
|
+
onSubmit: async ({ value, formApi }) => {
|
|
9304
|
+
await authClient.signIn.email(
|
|
9305
|
+
{
|
|
9306
|
+
email: value.email.trim(),
|
|
9307
|
+
password: value.password,
|
|
9308
|
+
},
|
|
9309
|
+
{
|
|
9310
|
+
onError(error) {
|
|
9311
|
+
toast.show({
|
|
9312
|
+
variant: "danger",
|
|
9313
|
+
label: error.error?.message || "Failed to sign in",
|
|
9314
|
+
});
|
|
9315
|
+
},
|
|
9316
|
+
onSuccess() {
|
|
9317
|
+
formApi.reset();
|
|
9318
|
+
toast.show({
|
|
9319
|
+
variant: "success",
|
|
9320
|
+
label: "Signed in successfully",
|
|
9321
|
+
});
|
|
9322
|
+
{{#if (eq api "orpc")}}
|
|
9323
|
+
queryClient.refetchQueries();
|
|
9324
|
+
{{/if}}
|
|
9325
|
+
{{#if (eq api "trpc")}}
|
|
9326
|
+
queryClient.refetchQueries();
|
|
9327
|
+
{{/if}}
|
|
9328
|
+
},
|
|
9329
|
+
},
|
|
9330
|
+
);
|
|
9331
|
+
},
|
|
9332
|
+
});
|
|
9333
|
+
|
|
8285
9334
|
return (
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
<ErrorView isInvalid={!!error} className="mb-3">
|
|
8290
|
-
{error}
|
|
8291
|
-
</ErrorView>
|
|
8292
|
-
|
|
8293
|
-
<View className="gap-3">
|
|
8294
|
-
<TextField>
|
|
8295
|
-
<TextField.Label>Email</TextField.Label>
|
|
8296
|
-
<TextField.Input
|
|
8297
|
-
value={email}
|
|
8298
|
-
onChangeText={setEmail}
|
|
8299
|
-
placeholder="email@example.com"
|
|
8300
|
-
keyboardType="email-address"
|
|
8301
|
-
autoCapitalize="none"
|
|
8302
|
-
/>
|
|
8303
|
-
</TextField>
|
|
8304
|
-
|
|
8305
|
-
<TextField>
|
|
8306
|
-
<TextField.Label>Password</TextField.Label>
|
|
8307
|
-
<TextField.Input
|
|
8308
|
-
value={password}
|
|
8309
|
-
onChangeText={setPassword}
|
|
8310
|
-
placeholder="••••••••"
|
|
8311
|
-
secureTextEntry
|
|
8312
|
-
/>
|
|
8313
|
-
</TextField>
|
|
9335
|
+
<Surface variant="secondary" className="p-4 rounded-lg">
|
|
9336
|
+
<Text className="text-foreground font-medium mb-4">Sign In</Text>
|
|
8314
9337
|
|
|
8315
|
-
<
|
|
8316
|
-
{
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
9338
|
+
<form.Subscribe
|
|
9339
|
+
selector={(state) => ({
|
|
9340
|
+
isSubmitting: state.isSubmitting,
|
|
9341
|
+
validationError: getErrorMessage(state.errorMap.onSubmit),
|
|
9342
|
+
})}
|
|
9343
|
+
>
|
|
9344
|
+
{({ isSubmitting, validationError }) => {
|
|
9345
|
+
const formError = validationError;
|
|
9346
|
+
|
|
9347
|
+
return (
|
|
9348
|
+
<>
|
|
9349
|
+
<FieldError isInvalid={!!formError} className="mb-3">
|
|
9350
|
+
{formError}
|
|
9351
|
+
</FieldError>
|
|
9352
|
+
|
|
9353
|
+
<View className="gap-3">
|
|
9354
|
+
<form.Field name="email">
|
|
9355
|
+
{(field) => (
|
|
9356
|
+
<TextField>
|
|
9357
|
+
<Label>Email</Label>
|
|
9358
|
+
<Input
|
|
9359
|
+
value={field.state.value}
|
|
9360
|
+
onBlur={field.handleBlur}
|
|
9361
|
+
onChangeText={field.handleChange}
|
|
9362
|
+
placeholder="email@example.com"
|
|
9363
|
+
keyboardType="email-address"
|
|
9364
|
+
autoCapitalize="none"
|
|
9365
|
+
autoComplete="email"
|
|
9366
|
+
textContentType="emailAddress"
|
|
9367
|
+
returnKeyType="next"
|
|
9368
|
+
blurOnSubmit={false}
|
|
9369
|
+
onSubmitEditing={() => {
|
|
9370
|
+
passwordInputRef.current?.focus();
|
|
9371
|
+
}}
|
|
9372
|
+
/>
|
|
9373
|
+
</TextField>
|
|
9374
|
+
)}
|
|
9375
|
+
</form.Field>
|
|
9376
|
+
|
|
9377
|
+
<form.Field name="password">
|
|
9378
|
+
{(field) => (
|
|
9379
|
+
<TextField>
|
|
9380
|
+
<Label>Password</Label>
|
|
9381
|
+
<Input
|
|
9382
|
+
ref={passwordInputRef}
|
|
9383
|
+
value={field.state.value}
|
|
9384
|
+
onBlur={field.handleBlur}
|
|
9385
|
+
onChangeText={field.handleChange}
|
|
9386
|
+
placeholder="••••••••"
|
|
9387
|
+
secureTextEntry
|
|
9388
|
+
autoComplete="password"
|
|
9389
|
+
textContentType="password"
|
|
9390
|
+
returnKeyType="go"
|
|
9391
|
+
onSubmitEditing={form.handleSubmit}
|
|
9392
|
+
/>
|
|
9393
|
+
</TextField>
|
|
9394
|
+
)}
|
|
9395
|
+
</form.Field>
|
|
9396
|
+
|
|
9397
|
+
<Button onPress={form.handleSubmit} isDisabled={isSubmitting} className="mt-1">
|
|
9398
|
+
{isSubmitting ? <Spinner size="sm" color="default" /> : <Button.Label>Sign In</Button.Label>}
|
|
9399
|
+
</Button>
|
|
9400
|
+
</View>
|
|
9401
|
+
</>
|
|
9402
|
+
);
|
|
9403
|
+
}}
|
|
9404
|
+
</form.Subscribe>
|
|
9405
|
+
</Surface>
|
|
8320
9406
|
);
|
|
8321
|
-
|
|
9407
|
+
}
|
|
8322
9408
|
|
|
8323
|
-
|
|
9409
|
+
export { SignIn };
|
|
9410
|
+
`],
|
|
8324
9411
|
["auth/better-auth/native/uniwind/components/sign-up.tsx.hbs", `import { authClient } from "@/lib/auth-client";
|
|
8325
9412
|
{{#if (eq api "trpc")}}
|
|
8326
9413
|
import { queryClient } from "@/utils/trpc";
|
|
@@ -8328,127 +9415,203 @@ import { queryClient } from "@/utils/trpc";
|
|
|
8328
9415
|
{{#if (eq api "orpc")}}
|
|
8329
9416
|
import { queryClient } from "@/utils/orpc";
|
|
8330
9417
|
{{/if}}
|
|
8331
|
-
import {
|
|
8332
|
-
import {
|
|
8333
|
-
import {
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
8341
|
-
|
|
8342
|
-
|
|
8343
|
-
|
|
8344
|
-
|
|
8345
|
-
|
|
8346
|
-
|
|
8347
|
-
|
|
8348
|
-
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
|
|
8355
|
-
|
|
8356
|
-
|
|
8357
|
-
|
|
8358
|
-
|
|
8359
|
-
|
|
8360
|
-
|
|
8361
|
-
|
|
8362
|
-
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
}
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
{
|
|
8373
|
-
|
|
8374
|
-
|
|
8375
|
-
|
|
8376
|
-
|
|
8377
|
-
|
|
8378
|
-
|
|
8379
|
-
onFinished() {
|
|
8380
|
-
setIsLoading(false);
|
|
8381
|
-
},
|
|
8382
|
-
}
|
|
8383
|
-
);
|
|
9418
|
+
import { useForm } from "@tanstack/react-form";
|
|
9419
|
+
import { useRef } from "react";
|
|
9420
|
+
import { Text, TextInput, View } from "react-native";
|
|
9421
|
+
import { Button, FieldError, Input, Label, Spinner, Surface, TextField, useToast } from "heroui-native";
|
|
9422
|
+
import z from "zod";
|
|
9423
|
+
|
|
9424
|
+
const signUpSchema = z.object({
|
|
9425
|
+
name: z
|
|
9426
|
+
.string()
|
|
9427
|
+
.trim()
|
|
9428
|
+
.min(1, "Name is required")
|
|
9429
|
+
.min(2, "Name must be at least 2 characters"),
|
|
9430
|
+
email: z
|
|
9431
|
+
.string()
|
|
9432
|
+
.trim()
|
|
9433
|
+
.min(1, "Email is required")
|
|
9434
|
+
.email("Enter a valid email address"),
|
|
9435
|
+
password: z
|
|
9436
|
+
.string()
|
|
9437
|
+
.min(1, "Password is required")
|
|
9438
|
+
.min(8, "Use at least 8 characters"),
|
|
9439
|
+
});
|
|
9440
|
+
|
|
9441
|
+
function getErrorMessage(error: unknown): string | null {
|
|
9442
|
+
if (!error) return null;
|
|
9443
|
+
|
|
9444
|
+
if (typeof error === "string") {
|
|
9445
|
+
return error;
|
|
9446
|
+
}
|
|
9447
|
+
|
|
9448
|
+
if (Array.isArray(error)) {
|
|
9449
|
+
for (const issue of error) {
|
|
9450
|
+
const message = getErrorMessage(issue);
|
|
9451
|
+
if (message) {
|
|
9452
|
+
return message;
|
|
9453
|
+
}
|
|
9454
|
+
}
|
|
9455
|
+
return null;
|
|
9456
|
+
}
|
|
9457
|
+
|
|
9458
|
+
if (typeof error === "object" && error !== null) {
|
|
9459
|
+
const maybeError = error as { message?: unknown };
|
|
9460
|
+
if (typeof maybeError.message === "string") {
|
|
9461
|
+
return maybeError.message;
|
|
9462
|
+
}
|
|
9463
|
+
}
|
|
9464
|
+
|
|
9465
|
+
return null;
|
|
8384
9466
|
}
|
|
8385
9467
|
|
|
8386
9468
|
export function SignUp() {
|
|
8387
|
-
const
|
|
8388
|
-
const
|
|
8389
|
-
const
|
|
8390
|
-
|
|
8391
|
-
const
|
|
8392
|
-
|
|
8393
|
-
|
|
8394
|
-
|
|
8395
|
-
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
|
|
8400
|
-
|
|
8401
|
-
|
|
8402
|
-
|
|
9469
|
+
const emailInputRef = useRef<TextInput>(null);
|
|
9470
|
+
const passwordInputRef = useRef<TextInput>(null);
|
|
9471
|
+
const { toast } = useToast();
|
|
9472
|
+
|
|
9473
|
+
const form = useForm({
|
|
9474
|
+
defaultValues: {
|
|
9475
|
+
name: "",
|
|
9476
|
+
email: "",
|
|
9477
|
+
password: "",
|
|
9478
|
+
},
|
|
9479
|
+
validators: {
|
|
9480
|
+
onSubmit: signUpSchema,
|
|
9481
|
+
},
|
|
9482
|
+
onSubmit: async ({ value, formApi }) => {
|
|
9483
|
+
await authClient.signUp.email(
|
|
9484
|
+
{
|
|
9485
|
+
name: value.name.trim(),
|
|
9486
|
+
email: value.email.trim(),
|
|
9487
|
+
password: value.password,
|
|
9488
|
+
},
|
|
9489
|
+
{
|
|
9490
|
+
onError(error) {
|
|
9491
|
+
toast.show({
|
|
9492
|
+
variant: "danger",
|
|
9493
|
+
label: error.error?.message || "Failed to sign up",
|
|
9494
|
+
});
|
|
9495
|
+
},
|
|
9496
|
+
onSuccess() {
|
|
9497
|
+
formApi.reset();
|
|
9498
|
+
toast.show({
|
|
9499
|
+
variant: "success",
|
|
9500
|
+
label: "Account created successfully",
|
|
9501
|
+
});
|
|
9502
|
+
{{#if (eq api "orpc")}}
|
|
9503
|
+
queryClient.refetchQueries();
|
|
9504
|
+
{{/if}}
|
|
9505
|
+
{{#if (eq api "trpc")}}
|
|
9506
|
+
queryClient.refetchQueries();
|
|
9507
|
+
{{/if}}
|
|
9508
|
+
},
|
|
9509
|
+
},
|
|
9510
|
+
);
|
|
9511
|
+
},
|
|
8403
9512
|
});
|
|
8404
|
-
}
|
|
8405
9513
|
|
|
8406
9514
|
return (
|
|
8407
|
-
|
|
8408
|
-
|
|
8409
|
-
|
|
8410
|
-
<ErrorView isInvalid={!!error} className="mb-3">
|
|
8411
|
-
{error}
|
|
8412
|
-
</ErrorView>
|
|
8413
|
-
|
|
8414
|
-
<View className="gap-3">
|
|
8415
|
-
<TextField>
|
|
8416
|
-
<TextField.Label>Name</TextField.Label>
|
|
8417
|
-
<TextField.Input value={name} onChangeText={setName} placeholder="John Doe" />
|
|
8418
|
-
</TextField>
|
|
8419
|
-
|
|
8420
|
-
<TextField>
|
|
8421
|
-
<TextField.Label>Email</TextField.Label>
|
|
8422
|
-
<TextField.Input
|
|
8423
|
-
value={email}
|
|
8424
|
-
onChangeText={setEmail}
|
|
8425
|
-
placeholder="email@example.com"
|
|
8426
|
-
keyboardType="email-address"
|
|
8427
|
-
autoCapitalize="none"
|
|
8428
|
-
/>
|
|
8429
|
-
</TextField>
|
|
8430
|
-
|
|
8431
|
-
<TextField>
|
|
8432
|
-
<TextField.Label>Password</TextField.Label>
|
|
8433
|
-
<TextField.Input
|
|
8434
|
-
value={password}
|
|
8435
|
-
onChangeText={setPassword}
|
|
8436
|
-
placeholder="••••••••"
|
|
8437
|
-
secureTextEntry
|
|
8438
|
-
/>
|
|
8439
|
-
</TextField>
|
|
9515
|
+
<Surface variant="secondary" className="p-4 rounded-lg">
|
|
9516
|
+
<Text className="text-foreground font-medium mb-4">Create Account</Text>
|
|
8440
9517
|
|
|
8441
|
-
<
|
|
8442
|
-
{
|
|
8443
|
-
|
|
8444
|
-
|
|
8445
|
-
|
|
8446
|
-
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
|
|
9518
|
+
<form.Subscribe
|
|
9519
|
+
selector={(state) => ({
|
|
9520
|
+
isSubmitting: state.isSubmitting,
|
|
9521
|
+
validationError: getErrorMessage(state.errorMap.onSubmit),
|
|
9522
|
+
})}
|
|
9523
|
+
>
|
|
9524
|
+
{({ isSubmitting, validationError }) => {
|
|
9525
|
+
const formError = validationError;
|
|
9526
|
+
|
|
9527
|
+
return (
|
|
9528
|
+
<>
|
|
9529
|
+
<FieldError isInvalid={!!formError} className="mb-3">
|
|
9530
|
+
{formError}
|
|
9531
|
+
</FieldError>
|
|
9532
|
+
|
|
9533
|
+
<View className="gap-3">
|
|
9534
|
+
<form.Field name="name">
|
|
9535
|
+
{(field) => (
|
|
9536
|
+
<TextField>
|
|
9537
|
+
<Label>Name</Label>
|
|
9538
|
+
<Input
|
|
9539
|
+
value={field.state.value}
|
|
9540
|
+
onBlur={field.handleBlur}
|
|
9541
|
+
onChangeText={field.handleChange}
|
|
9542
|
+
placeholder="John Doe"
|
|
9543
|
+
autoComplete="name"
|
|
9544
|
+
textContentType="name"
|
|
9545
|
+
returnKeyType="next"
|
|
9546
|
+
blurOnSubmit={false}
|
|
9547
|
+
onSubmitEditing={() => {
|
|
9548
|
+
emailInputRef.current?.focus();
|
|
9549
|
+
}}
|
|
9550
|
+
/>
|
|
9551
|
+
</TextField>
|
|
9552
|
+
)}
|
|
9553
|
+
</form.Field>
|
|
9554
|
+
|
|
9555
|
+
<form.Field name="email">
|
|
9556
|
+
{(field) => (
|
|
9557
|
+
<TextField>
|
|
9558
|
+
<Label>Email</Label>
|
|
9559
|
+
<Input
|
|
9560
|
+
ref={emailInputRef}
|
|
9561
|
+
value={field.state.value}
|
|
9562
|
+
onBlur={field.handleBlur}
|
|
9563
|
+
onChangeText={field.handleChange}
|
|
9564
|
+
placeholder="email@example.com"
|
|
9565
|
+
keyboardType="email-address"
|
|
9566
|
+
autoCapitalize="none"
|
|
9567
|
+
autoComplete="email"
|
|
9568
|
+
textContentType="emailAddress"
|
|
9569
|
+
returnKeyType="next"
|
|
9570
|
+
blurOnSubmit={false}
|
|
9571
|
+
onSubmitEditing={() => {
|
|
9572
|
+
passwordInputRef.current?.focus();
|
|
9573
|
+
}}
|
|
9574
|
+
/>
|
|
9575
|
+
</TextField>
|
|
9576
|
+
)}
|
|
9577
|
+
</form.Field>
|
|
9578
|
+
|
|
9579
|
+
<form.Field name="password">
|
|
9580
|
+
{(field) => (
|
|
9581
|
+
<TextField>
|
|
9582
|
+
<Label>Password</Label>
|
|
9583
|
+
<Input
|
|
9584
|
+
ref={passwordInputRef}
|
|
9585
|
+
value={field.state.value}
|
|
9586
|
+
onBlur={field.handleBlur}
|
|
9587
|
+
onChangeText={field.handleChange}
|
|
9588
|
+
placeholder="••••••••"
|
|
9589
|
+
secureTextEntry
|
|
9590
|
+
autoComplete="new-password"
|
|
9591
|
+
textContentType="newPassword"
|
|
9592
|
+
returnKeyType="go"
|
|
9593
|
+
onSubmitEditing={form.handleSubmit}
|
|
9594
|
+
/>
|
|
9595
|
+
</TextField>
|
|
9596
|
+
)}
|
|
9597
|
+
</form.Field>
|
|
9598
|
+
|
|
9599
|
+
<Button onPress={form.handleSubmit} isDisabled={isSubmitting} className="mt-1">
|
|
9600
|
+
{isSubmitting ? (
|
|
9601
|
+
<Spinner size="sm" color="default" />
|
|
9602
|
+
) : (
|
|
9603
|
+
<Button.Label>Create Account</Button.Label>
|
|
9604
|
+
)}
|
|
9605
|
+
</Button>
|
|
9606
|
+
</View>
|
|
9607
|
+
</>
|
|
9608
|
+
);
|
|
9609
|
+
}}
|
|
9610
|
+
</form.Subscribe>
|
|
9611
|
+
</Surface>
|
|
8450
9612
|
);
|
|
8451
|
-
|
|
9613
|
+
}
|
|
9614
|
+
`],
|
|
8452
9615
|
["auth/better-auth/server/base/_gitignore", `# dependencies (bun install)
|
|
8453
9616
|
node_modules
|
|
8454
9617
|
|
|
@@ -14561,14 +15724,8 @@ import { env } from "@{{projectName}}/env/server";
|
|
|
14561
15724
|
import * as schema from "./schema";
|
|
14562
15725
|
|
|
14563
15726
|
{{#if (eq dbSetup "neon")}}
|
|
14564
|
-
import { neon
|
|
15727
|
+
import { neon } from '@neondatabase/serverless';
|
|
14565
15728
|
import { drizzle } from 'drizzle-orm/neon-http';
|
|
14566
|
-
import ws from "ws";
|
|
14567
|
-
|
|
14568
|
-
neonConfig.webSocketConstructor = ws;
|
|
14569
|
-
|
|
14570
|
-
// To work in edge environments (Cloudflare Workers, Vercel Edge, etc.), enable querying over fetch
|
|
14571
|
-
// neonConfig.poolQueryViaFetch = true
|
|
14572
15729
|
|
|
14573
15730
|
const sql = neon(env.DATABASE_URL);
|
|
14574
15731
|
export const db = drizzle(sql, { schema });
|
|
@@ -14583,13 +15740,9 @@ export const db = drizzle(env.DATABASE_URL, { schema });
|
|
|
14583
15740
|
import * as schema from "./schema";
|
|
14584
15741
|
|
|
14585
15742
|
{{#if (eq dbSetup "neon")}}
|
|
14586
|
-
import { neon
|
|
15743
|
+
import { neon } from '@neondatabase/serverless';
|
|
14587
15744
|
import { drizzle } from 'drizzle-orm/neon-http';
|
|
14588
15745
|
import { env } from "@{{projectName}}/env/server";
|
|
14589
|
-
import ws from "ws";
|
|
14590
|
-
|
|
14591
|
-
neonConfig.webSocketConstructor = ws;
|
|
14592
|
-
neonConfig.poolQueryViaFetch = true;
|
|
14593
15746
|
|
|
14594
15747
|
const sql = neon(env.DATABASE_URL || "");
|
|
14595
15748
|
export const db = drizzle(sql, { schema });
|
|
@@ -14904,11 +16057,6 @@ import { PrismaClient } from "../prisma/generated/client";
|
|
|
14904
16057
|
import { env } from "@{{projectName}}/env/server";
|
|
14905
16058
|
{{#if (eq dbSetup "neon")}}
|
|
14906
16059
|
import { PrismaNeon } from "@prisma/adapter-neon";
|
|
14907
|
-
import { neonConfig } from "@neondatabase/serverless";
|
|
14908
|
-
import ws from "ws";
|
|
14909
|
-
|
|
14910
|
-
neonConfig.webSocketConstructor = ws;
|
|
14911
|
-
neonConfig.poolQueryViaFetch = true;
|
|
14912
16060
|
|
|
14913
16061
|
const adapter = new PrismaNeon({
|
|
14914
16062
|
connectionString: env.DATABASE_URL,
|
|
@@ -16392,7 +17540,7 @@ import {
|
|
|
16392
17540
|
} from "@convex-dev/agent/react";
|
|
16393
17541
|
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
16394
17542
|
import { useMutation } from "convex/react";
|
|
16395
|
-
import { Button,
|
|
17543
|
+
import { Button, Separator, Spinner, Surface, Input, TextField, useThemeColor } from "heroui-native";
|
|
16396
17544
|
import { useRef, useEffect, useState } from "react";
|
|
16397
17545
|
import {
|
|
16398
17546
|
View,
|
|
@@ -16465,7 +17613,7 @@ export default function AIScreen() {
|
|
|
16465
17613
|
};
|
|
16466
17614
|
|
|
16467
17615
|
return (
|
|
16468
|
-
<Container>
|
|
17616
|
+
<Container isScrollable={false}>
|
|
16469
17617
|
<KeyboardAvoidingView
|
|
16470
17618
|
className="flex-1"
|
|
16471
17619
|
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
|
@@ -16480,19 +17628,21 @@ export default function AIScreen() {
|
|
|
16480
17628
|
ref={scrollViewRef}
|
|
16481
17629
|
className="flex-1 mb-4"
|
|
16482
17630
|
showsVerticalScrollIndicator={false}
|
|
17631
|
+
contentContainerStyle=\\{{ flexGrow: 1, paddingBottom: 8 }}
|
|
17632
|
+
keyboardShouldPersistTaps="handled"
|
|
16483
17633
|
>
|
|
16484
17634
|
{!messages || messages.length === 0 ? (
|
|
16485
|
-
<
|
|
17635
|
+
<Surface variant="secondary" className="flex-1 justify-center items-center py-8 rounded-xl">
|
|
16486
17636
|
<Ionicons name="chatbubble-ellipses-outline" size={32} color={mutedColor} />
|
|
16487
17637
|
<Text className="text-muted text-sm mt-3">Ask me anything to get started</Text>
|
|
16488
|
-
</
|
|
17638
|
+
</Surface>
|
|
16489
17639
|
) : (
|
|
16490
|
-
<View className="gap-
|
|
17640
|
+
<View className="gap-3">
|
|
16491
17641
|
{messages.map((message: UIMessage) => (
|
|
16492
17642
|
<Surface
|
|
16493
17643
|
key={message.key}
|
|
16494
17644
|
variant={message.role === "user" ? "tertiary" : "secondary"}
|
|
16495
|
-
className={\`p-3 rounded-
|
|
17645
|
+
className={\`p-3 rounded-xl \${message.role === "user" ? "ml-8" : "mr-8"}\`}
|
|
16496
17646
|
>
|
|
16497
17647
|
<Text className="text-xs font-medium mb-1 text-muted">
|
|
16498
17648
|
{message.role === "user" ? "You" : "AI"}
|
|
@@ -16516,21 +17666,21 @@ export default function AIScreen() {
|
|
|
16516
17666
|
)}
|
|
16517
17667
|
</ScrollView>
|
|
16518
17668
|
|
|
16519
|
-
<
|
|
17669
|
+
<Separator className="mb-3" />
|
|
16520
17670
|
|
|
16521
17671
|
<View className="flex-row items-center gap-2">
|
|
16522
17672
|
<View className="flex-1">
|
|
16523
|
-
|
|
16524
|
-
|
|
16525
|
-
|
|
16526
|
-
|
|
16527
|
-
|
|
16528
|
-
|
|
16529
|
-
|
|
16530
|
-
|
|
16531
|
-
|
|
16532
|
-
|
|
16533
|
-
|
|
17673
|
+
<TextField>
|
|
17674
|
+
<Input
|
|
17675
|
+
value={input}
|
|
17676
|
+
onChangeText={setInput}
|
|
17677
|
+
placeholder="Type a message..."
|
|
17678
|
+
onSubmitEditing={onSubmit}
|
|
17679
|
+
editable={!isLoading}
|
|
17680
|
+
returnKeyType="send"
|
|
17681
|
+
/>
|
|
17682
|
+
</TextField>
|
|
17683
|
+
</View>
|
|
16534
17684
|
<Button
|
|
16535
17685
|
isIconOnly
|
|
16536
17686
|
variant={input.trim() && !isLoading ? "primary" : "secondary"}
|
|
@@ -16564,7 +17714,7 @@ import { DefaultChatTransport } from "ai";
|
|
|
16564
17714
|
import { fetch as expoFetch } from "expo/fetch";
|
|
16565
17715
|
import { Ionicons } from "@expo/vector-icons";
|
|
16566
17716
|
import { Container } from "@/components/container";
|
|
16567
|
-
import { Button,
|
|
17717
|
+
import { Button, Separator, FieldError, Spinner, Surface, Input, TextField, useThemeColor } from "heroui-native";
|
|
16568
17718
|
import { env } from "@{{projectName}}/env/native";
|
|
16569
17719
|
|
|
16570
17720
|
const generateAPIUrl = (relativePath: string) => {
|
|
@@ -16580,7 +17730,7 @@ const generateAPIUrl = (relativePath: string) => {
|
|
|
16580
17730
|
|
|
16581
17731
|
export default function AIScreen() {
|
|
16582
17732
|
const [input, setInput] = useState("");
|
|
16583
|
-
const { messages, error, sendMessage } = useChat({
|
|
17733
|
+
const { messages, error, sendMessage, status } = useChat({
|
|
16584
17734
|
transport: new DefaultChatTransport({
|
|
16585
17735
|
fetch: expoFetch as unknown as typeof globalThis.fetch,
|
|
16586
17736
|
api: generateAPIUrl("/ai"),
|
|
@@ -16590,6 +17740,7 @@ export default function AIScreen() {
|
|
|
16590
17740
|
const scrollViewRef = useRef<ScrollView>(null);
|
|
16591
17741
|
const foregroundColor = useThemeColor("foreground");
|
|
16592
17742
|
const mutedColor = useThemeColor("muted");
|
|
17743
|
+
const isBusy = status === "submitted" || status === "streaming";
|
|
16593
17744
|
|
|
16594
17745
|
useEffect(() => {
|
|
16595
17746
|
scrollViewRef.current?.scrollToEnd({ animated: true });
|
|
@@ -16597,7 +17748,7 @@ export default function AIScreen() {
|
|
|
16597
17748
|
|
|
16598
17749
|
const onSubmit = () => {
|
|
16599
17750
|
const value = input.trim();
|
|
16600
|
-
if (value) {
|
|
17751
|
+
if (value && !isBusy) {
|
|
16601
17752
|
sendMessage({ text: value });
|
|
16602
17753
|
setInput("");
|
|
16603
17754
|
}
|
|
@@ -16605,17 +17756,17 @@ export default function AIScreen() {
|
|
|
16605
17756
|
|
|
16606
17757
|
if (error) {
|
|
16607
17758
|
return (
|
|
16608
|
-
<Container>
|
|
17759
|
+
<Container isScrollable={false}>
|
|
16609
17760
|
<View className="flex-1 justify-center items-center px-4">
|
|
16610
17761
|
<Surface variant="secondary" className="p-4 rounded-lg">
|
|
16611
|
-
<
|
|
17762
|
+
<FieldError isInvalid>
|
|
16612
17763
|
<Text className="text-danger text-center font-medium mb-1">
|
|
16613
17764
|
{error.message}
|
|
16614
17765
|
</Text>
|
|
16615
17766
|
<Text className="text-muted text-center text-xs">
|
|
16616
17767
|
Please check your connection and try again.
|
|
16617
17768
|
</Text>
|
|
16618
|
-
</
|
|
17769
|
+
</FieldError>
|
|
16619
17770
|
</Surface>
|
|
16620
17771
|
</View>
|
|
16621
17772
|
</Container>
|
|
@@ -16623,7 +17774,7 @@ export default function AIScreen() {
|
|
|
16623
17774
|
}
|
|
16624
17775
|
|
|
16625
17776
|
return (
|
|
16626
|
-
<Container>
|
|
17777
|
+
<Container isScrollable={false}>
|
|
16627
17778
|
<KeyboardAvoidingView
|
|
16628
17779
|
className="flex-1"
|
|
16629
17780
|
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
|
@@ -16638,19 +17789,21 @@ export default function AIScreen() {
|
|
|
16638
17789
|
ref={scrollViewRef}
|
|
16639
17790
|
className="flex-1 mb-4"
|
|
16640
17791
|
showsVerticalScrollIndicator={false}
|
|
17792
|
+
contentContainerStyle=\\{{ flexGrow: 1, paddingBottom: 8 }}
|
|
17793
|
+
keyboardShouldPersistTaps="handled"
|
|
16641
17794
|
>
|
|
16642
17795
|
{messages.length === 0 ? (
|
|
16643
|
-
<
|
|
17796
|
+
<Surface variant="secondary" className="flex-1 justify-center items-center py-8 rounded-xl">
|
|
16644
17797
|
<Ionicons name="chatbubble-ellipses-outline" size={32} color={mutedColor} />
|
|
16645
17798
|
<Text className="text-muted text-sm mt-3">Ask me anything to get started</Text>
|
|
16646
|
-
</
|
|
17799
|
+
</Surface>
|
|
16647
17800
|
) : (
|
|
16648
|
-
<View className="gap-
|
|
17801
|
+
<View className="gap-3">
|
|
16649
17802
|
{messages.map((message) => (
|
|
16650
17803
|
<Surface
|
|
16651
17804
|
key={message.id}
|
|
16652
17805
|
variant={message.role === "user" ? "tertiary" : "secondary"}
|
|
16653
|
-
className={\`p-3 rounded-
|
|
17806
|
+
className={\`p-3 rounded-xl \${message.role === "user" ? "ml-8" : "mr-8"}\`}
|
|
16654
17807
|
>
|
|
16655
17808
|
<Text className="text-xs font-medium mb-1 text-muted">
|
|
16656
17809
|
{message.role === "user" ? "You" : "AI"}
|
|
@@ -16676,35 +17829,45 @@ export default function AIScreen() {
|
|
|
16676
17829
|
</View>
|
|
16677
17830
|
</Surface>
|
|
16678
17831
|
))}
|
|
17832
|
+
{isBusy && (
|
|
17833
|
+
<Surface variant="secondary" className="p-3 mr-8 rounded-xl">
|
|
17834
|
+
<Text className="text-xs font-medium mb-1 text-muted">AI</Text>
|
|
17835
|
+
<View className="flex-row items-center gap-2">
|
|
17836
|
+
<Spinner size="sm" />
|
|
17837
|
+
<Text className="text-muted text-sm">Thinking...</Text>
|
|
17838
|
+
</View>
|
|
17839
|
+
</Surface>
|
|
17840
|
+
)}
|
|
16679
17841
|
</View>
|
|
16680
17842
|
)}
|
|
16681
17843
|
</ScrollView>
|
|
16682
17844
|
|
|
16683
|
-
<
|
|
17845
|
+
<Separator className="mb-3" />
|
|
16684
17846
|
|
|
16685
17847
|
<View className="flex-row items-center gap-2">
|
|
16686
17848
|
<View className="flex-1">
|
|
16687
17849
|
<TextField>
|
|
16688
|
-
<
|
|
17850
|
+
<Input
|
|
16689
17851
|
value={input}
|
|
16690
17852
|
onChangeText={setInput}
|
|
16691
17853
|
placeholder="Type a message..."
|
|
16692
17854
|
onSubmitEditing={onSubmit}
|
|
16693
|
-
|
|
17855
|
+
returnKeyType="send"
|
|
17856
|
+
editable={!isBusy}
|
|
16694
17857
|
/>
|
|
16695
17858
|
</TextField>
|
|
16696
17859
|
</View>
|
|
16697
17860
|
<Button
|
|
16698
17861
|
isIconOnly
|
|
16699
|
-
variant={input.trim() ? "primary" : "secondary"}
|
|
17862
|
+
variant={input.trim() && !isBusy ? "primary" : "secondary"}
|
|
16700
17863
|
onPress={onSubmit}
|
|
16701
|
-
isDisabled={!input.trim()}
|
|
17864
|
+
isDisabled={!input.trim() || isBusy}
|
|
16702
17865
|
size="sm"
|
|
16703
17866
|
>
|
|
16704
17867
|
<Ionicons
|
|
16705
17868
|
name="arrow-up"
|
|
16706
17869
|
size={18}
|
|
16707
|
-
color={input.trim() ? foregroundColor : mutedColor}
|
|
17870
|
+
color={input.trim() && !isBusy ? foregroundColor : mutedColor}
|
|
16708
17871
|
/>
|
|
16709
17872
|
</Button>
|
|
16710
17873
|
</View>
|
|
@@ -18814,7 +19977,7 @@ import { Container } from "@/components/container";
|
|
|
18814
19977
|
import { trpc } from "@/utils/trpc";
|
|
18815
19978
|
{{/if}}
|
|
18816
19979
|
{{/unless}}
|
|
18817
|
-
import { Button, Checkbox, Chip, Spinner, Surface, TextField, useThemeColor } from "heroui-native";
|
|
19980
|
+
import { Button, Checkbox, Chip, Spinner, Surface, Input, TextField, useThemeColor } from "heroui-native";
|
|
18818
19981
|
|
|
18819
19982
|
export default function TodosScreen() {
|
|
18820
19983
|
const [newTodoText, setNewTodoText] = useState("");
|
|
@@ -18941,7 +20104,7 @@ export default function TodosScreen() {
|
|
|
18941
20104
|
<View className="flex-row items-center gap-2">
|
|
18942
20105
|
<View className="flex-1">
|
|
18943
20106
|
<TextField>
|
|
18944
|
-
<
|
|
20107
|
+
<Input
|
|
18945
20108
|
value={newTodoText}
|
|
18946
20109
|
onChangeText={setNewTodoText}
|
|
18947
20110
|
placeholder="Add a new task..."
|
|
@@ -22517,7 +23680,6 @@ module.exports = config;
|
|
|
22517
23680
|
"@react-navigation/bottom-tabs": "^7.2.0",
|
|
22518
23681
|
"@react-navigation/drawer": "^7.1.1",
|
|
22519
23682
|
"@react-navigation/native": "^7.0.14",
|
|
22520
|
-
"@tanstack/react-form": "^1.0.5",
|
|
22521
23683
|
"@tanstack/react-query": "^5.85.5",
|
|
22522
23684
|
{{#if (includes examples "ai")}}
|
|
22523
23685
|
"@stardazed/streams-text-encoding": "^1.0.2",
|
|
@@ -22551,7 +23713,6 @@ module.exports = config;
|
|
|
22551
23713
|
},
|
|
22552
23714
|
"private": true
|
|
22553
23715
|
}
|
|
22554
|
-
|
|
22555
23716
|
`],
|
|
22556
23717
|
["frontend/native/bare/tsconfig.json.hbs", `{
|
|
22557
23718
|
"extends": "expo/tsconfig.base",
|
|
@@ -23579,7 +24740,6 @@ module.exports = config;
|
|
|
23579
24740
|
"@stardazed/streams-text-encoding": "^1.0.2",
|
|
23580
24741
|
"@ungap/structured-clone": "^1.3.0",
|
|
23581
24742
|
{{/if}}
|
|
23582
|
-
"@tanstack/react-form": "^1.0.5",
|
|
23583
24743
|
"babel-preset-expo": "~54.0.10",
|
|
23584
24744
|
"expo": "~54.0.33",
|
|
23585
24745
|
"expo-constants": "~18.0.8",
|
|
@@ -24126,7 +25286,7 @@ import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
|
24126
25286
|
{{#unless (or (eq backend "none") (and (eq backend "convex") (eq auth "better-auth")))}}
|
|
24127
25287
|
import { Ionicons } from "@expo/vector-icons";
|
|
24128
25288
|
{{/unless}}
|
|
24129
|
-
import { Button, Chip,
|
|
25289
|
+
import { Button, Chip, Separator, Spinner, Surface, useThemeColor } from "heroui-native";
|
|
24130
25290
|
|
|
24131
25291
|
export default function Home() {
|
|
24132
25292
|
{{#if (eq api "orpc")}}
|
|
@@ -24162,8 +25322,8 @@ const isLoading = healthCheck?.isLoading;
|
|
|
24162
25322
|
{{/unless}}
|
|
24163
25323
|
|
|
24164
25324
|
return (
|
|
24165
|
-
<Container className="
|
|
24166
|
-
<View className="py-6 mb-
|
|
25325
|
+
<Container className="px-4 pb-4">
|
|
25326
|
+
<View className="py-6 mb-5">
|
|
24167
25327
|
<Text className="text-3xl font-semibold text-foreground tracking-tight">
|
|
24168
25328
|
Better T Stack
|
|
24169
25329
|
</Text>
|
|
@@ -24171,7 +25331,7 @@ return (
|
|
|
24171
25331
|
</View>
|
|
24172
25332
|
|
|
24173
25333
|
{{#unless (or (eq backend "none") (and (eq backend "convex") (eq auth "better-auth")))}}
|
|
24174
|
-
<Surface variant="secondary" className="p-4 rounded-
|
|
25334
|
+
<Surface variant="secondary" className="p-4 rounded-xl">
|
|
24175
25335
|
<View className="flex-row items-center justify-between mb-3">
|
|
24176
25336
|
<Text className="text-foreground font-medium">System Status</Text>
|
|
24177
25337
|
<Chip variant="secondary" color={isConnected ? "success" : "danger" } size="sm">
|
|
@@ -24181,9 +25341,9 @@ return (
|
|
|
24181
25341
|
</Chip>
|
|
24182
25342
|
</View>
|
|
24183
25343
|
|
|
24184
|
-
<
|
|
25344
|
+
<Separator className="mb-3" />
|
|
24185
25345
|
|
|
24186
|
-
<Surface variant="tertiary" className="p-3 rounded-
|
|
25346
|
+
<Surface variant="tertiary" className="p-3 rounded-lg">
|
|
24187
25347
|
<View className="flex-row items-center">
|
|
24188
25348
|
<View className={\`w-2 h-2 rounded-full mr-3 \${ isConnected ? "bg-success" : "bg-muted" }\`} />
|
|
24189
25349
|
<View className="flex-1">
|
|
@@ -24218,7 +25378,7 @@ return (
|
|
|
24218
25378
|
|
|
24219
25379
|
{{#if (and (eq backend "convex") (eq auth "clerk"))}}
|
|
24220
25380
|
<Authenticated>
|
|
24221
|
-
<Surface variant="secondary" className="mt-
|
|
25381
|
+
<Surface variant="secondary" className="mt-5 p-4 rounded-xl">
|
|
24222
25382
|
<View className="flex-row items-center justify-between">
|
|
24223
25383
|
<View className="flex-1">
|
|
24224
25384
|
<Text className="text-foreground font-medium">{user?.emailAddresses[0].emailAddress}</Text>
|
|
@@ -24247,14 +25407,14 @@ return (
|
|
|
24247
25407
|
|
|
24248
25408
|
{{#if (and (eq backend "convex") (eq auth "better-auth"))}}
|
|
24249
25409
|
{user ? (
|
|
24250
|
-
<Surface variant="secondary" className="mb-4 p-4 rounded-
|
|
25410
|
+
<Surface variant="secondary" className="mb-4 p-4 rounded-xl">
|
|
24251
25411
|
<View className="flex-row items-center justify-between">
|
|
24252
25412
|
<View className="flex-1">
|
|
24253
25413
|
<Text className="text-foreground font-medium">{user.name}</Text>
|
|
24254
25414
|
<Text className="text-muted text-xs mt-0.5">{user.email}</Text>
|
|
24255
25415
|
</View>
|
|
24256
25416
|
<Button
|
|
24257
|
-
variant="
|
|
25417
|
+
variant="danger"
|
|
24258
25418
|
size="sm"
|
|
24259
25419
|
onPress={() => {
|
|
24260
25420
|
authClient.signOut();
|
|
@@ -24265,7 +25425,7 @@ return (
|
|
|
24265
25425
|
</View>
|
|
24266
25426
|
</Surface>
|
|
24267
25427
|
) : null}
|
|
24268
|
-
<Surface variant="secondary" className="p-4 rounded-
|
|
25428
|
+
<Surface variant="secondary" className="p-4 rounded-xl">
|
|
24269
25429
|
<Text className="text-foreground font-medium mb-2">API Status</Text>
|
|
24270
25430
|
<View className="flex-row items-center gap-2">
|
|
24271
25431
|
<View className={\`w-2 h-2 rounded-full \${healthCheck==="OK" ? "bg-success" : "bg-danger" }\`} />
|
|
@@ -24279,7 +25439,7 @@ return (
|
|
|
24279
25439
|
</View>
|
|
24280
25440
|
</Surface>
|
|
24281
25441
|
{!user && (
|
|
24282
|
-
<View className="mt-
|
|
25442
|
+
<View className="mt-5 gap-4">
|
|
24283
25443
|
<SignIn />
|
|
24284
25444
|
<SignUp />
|
|
24285
25445
|
</View>
|
|
@@ -24287,7 +25447,8 @@ return (
|
|
|
24287
25447
|
{{/if}}
|
|
24288
25448
|
</Container>
|
|
24289
25449
|
);
|
|
24290
|
-
}
|
|
25450
|
+
}
|
|
25451
|
+
`],
|
|
24291
25452
|
["frontend/native/uniwind/app/+not-found.tsx.hbs", `import { Link, Stack } from "expo-router";
|
|
24292
25453
|
import { Button, Surface } from "heroui-native";
|
|
24293
25454
|
import { Text, View } from "react-native";
|
|
@@ -24356,36 +25517,49 @@ export default Modal;
|
|
|
24356
25517
|
`],
|
|
24357
25518
|
["frontend/native/uniwind/components/container.tsx.hbs", `import { cn } from "heroui-native";
|
|
24358
25519
|
import { type PropsWithChildren } from "react";
|
|
24359
|
-
import { ScrollView, View, type ViewProps } from "react-native";
|
|
25520
|
+
import { ScrollView, View, type ScrollViewProps, type ViewProps } from "react-native";
|
|
24360
25521
|
import Animated, { type AnimatedProps } from "react-native-reanimated";
|
|
24361
25522
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
24362
25523
|
|
|
24363
25524
|
const AnimatedView = Animated.createAnimatedComponent(View);
|
|
24364
25525
|
|
|
24365
25526
|
type Props = AnimatedProps<ViewProps> & {
|
|
24366
|
-
|
|
25527
|
+
className?: string;
|
|
25528
|
+
isScrollable?: boolean;
|
|
25529
|
+
scrollViewProps?: Omit<ScrollViewProps, "contentContainerStyle">;
|
|
24367
25530
|
};
|
|
24368
25531
|
|
|
24369
25532
|
export function Container({
|
|
24370
|
-
|
|
24371
|
-
|
|
24372
|
-
|
|
25533
|
+
children,
|
|
25534
|
+
className,
|
|
25535
|
+
isScrollable = true,
|
|
25536
|
+
scrollViewProps,
|
|
25537
|
+
...props
|
|
24373
25538
|
}: PropsWithChildren<Props>) {
|
|
24374
|
-
|
|
25539
|
+
const insets = useSafeAreaInsets();
|
|
24375
25540
|
|
|
24376
|
-
|
|
24377
|
-
|
|
24378
|
-
|
|
24379
|
-
|
|
24380
|
-
|
|
24381
|
-
|
|
24382
|
-
|
|
24383
|
-
|
|
24384
|
-
|
|
24385
|
-
|
|
24386
|
-
|
|
24387
|
-
|
|
24388
|
-
|
|
25541
|
+
return (
|
|
25542
|
+
<AnimatedView
|
|
25543
|
+
className={cn("flex-1 bg-background", className)}
|
|
25544
|
+
style=\\{{
|
|
25545
|
+
paddingBottom: insets.bottom,
|
|
25546
|
+
}}
|
|
25547
|
+
{...props}
|
|
25548
|
+
>
|
|
25549
|
+
{isScrollable ? (
|
|
25550
|
+
<ScrollView
|
|
25551
|
+
contentContainerStyle=\\{{ flexGrow: 1 }}
|
|
25552
|
+
keyboardShouldPersistTaps="handled"
|
|
25553
|
+
contentInsetAdjustmentBehavior="automatic"
|
|
25554
|
+
{...scrollViewProps}
|
|
25555
|
+
>
|
|
25556
|
+
{children}
|
|
25557
|
+
</ScrollView>
|
|
25558
|
+
) : (
|
|
25559
|
+
<View className="flex-1">{children}</View>
|
|
25560
|
+
)}
|
|
25561
|
+
</AnimatedView>
|
|
25562
|
+
);
|
|
24389
25563
|
}
|
|
24390
25564
|
`],
|
|
24391
25565
|
["frontend/native/uniwind/components/theme-toggle.tsx.hbs", `import { Ionicons } from '@expo/vector-icons';
|
|
@@ -24495,17 +25669,17 @@ export function useAppTheme() {
|
|
|
24495
25669
|
`],
|
|
24496
25670
|
["frontend/native/uniwind/metro.config.js.hbs", `const { getDefaultConfig } = require("expo/metro-config");
|
|
24497
25671
|
const { withUniwindConfig } = require("uniwind/metro");
|
|
25672
|
+
const { wrapWithReanimatedMetroConfig } = require("react-native-reanimated/metro-config");
|
|
24498
25673
|
|
|
24499
25674
|
/** @type {import('expo/metro-config').MetroConfig} */
|
|
24500
25675
|
const config = getDefaultConfig(__dirname);
|
|
24501
25676
|
|
|
24502
|
-
const uniwindConfig = withUniwindConfig(config, {
|
|
25677
|
+
const uniwindConfig = withUniwindConfig(wrapWithReanimatedMetroConfig(config), {
|
|
24503
25678
|
cssEntryFile: "./global.css",
|
|
24504
25679
|
dtsFile: "./uniwind-types.d.ts",
|
|
24505
25680
|
});
|
|
24506
25681
|
|
|
24507
25682
|
module.exports = uniwindConfig;
|
|
24508
|
-
|
|
24509
25683
|
`],
|
|
24510
25684
|
["frontend/native/uniwind/package.json.hbs", `{
|
|
24511
25685
|
"name": "native",
|
|
@@ -24539,7 +25713,7 @@ module.exports = uniwindConfig;
|
|
|
24539
25713
|
"expo-router": "~6.0.14",
|
|
24540
25714
|
"expo-secure-store": "~15.0.7",
|
|
24541
25715
|
"expo-status-bar": "~3.0.8",
|
|
24542
|
-
"heroui-native": "^1.0.0-
|
|
25716
|
+
"heroui-native": "^1.0.0-rc.1",
|
|
24543
25717
|
"react": "19.1.0",
|
|
24544
25718
|
"react-dom": "19.1.0",
|
|
24545
25719
|
"react-native": "0.81.5",
|
|
@@ -24554,13 +25728,14 @@ module.exports = uniwindConfig;
|
|
|
24554
25728
|
"tailwind-merge": "^3.4.0",
|
|
24555
25729
|
"tailwind-variants": "^3.2.2",
|
|
24556
25730
|
"tailwindcss": "^4.1.18",
|
|
24557
|
-
"uniwind": "^1.
|
|
25731
|
+
"uniwind": "^1.3.0"
|
|
24558
25732
|
},
|
|
24559
25733
|
"devDependencies": {
|
|
24560
25734
|
"@types/node": "^24.10.0",
|
|
24561
25735
|
"@types/react": "~19.1.0"
|
|
24562
25736
|
}
|
|
24563
|
-
}
|
|
25737
|
+
}
|
|
25738
|
+
`],
|
|
24564
25739
|
["frontend/native/uniwind/tsconfig.json.hbs", `{
|
|
24565
25740
|
"extends": "expo/tsconfig.base",
|
|
24566
25741
|
"compilerOptions": {
|
|
@@ -24913,7 +26088,6 @@ initOpenNextCloudflareForDev();
|
|
|
24913
26088
|
"dependencies": {
|
|
24914
26089
|
"@base-ui/react": "^1.0.0",
|
|
24915
26090
|
"shadcn": "^3.6.2",
|
|
24916
|
-
"@tanstack/react-form": "^1.27.3",
|
|
24917
26091
|
"class-variance-authority": "^0.7.1",
|
|
24918
26092
|
"clsx": "^2.1.1",
|
|
24919
26093
|
"lucide-react": "^0.546.0",
|
|
@@ -25298,7 +26472,6 @@ export function ThemeProvider({
|
|
|
25298
26472
|
"@react-router/fs-routes": "^7.10.1",
|
|
25299
26473
|
"@react-router/node": "^7.10.1",
|
|
25300
26474
|
"@react-router/serve": "^7.10.1",
|
|
25301
|
-
"@tanstack/react-form": "^1.27.3",
|
|
25302
26475
|
"class-variance-authority": "^0.7.1",
|
|
25303
26476
|
"clsx": "^2.1.1",
|
|
25304
26477
|
"isbot": "^5.1.28",
|
|
@@ -25727,7 +26900,6 @@ export default defineConfig({
|
|
|
25727
26900
|
"@hookform/resolvers": "^5.1.1",
|
|
25728
26901
|
"@base-ui/react": "^1.0.0",
|
|
25729
26902
|
"shadcn": "^3.6.2",
|
|
25730
|
-
"@tanstack/react-form": "^1.12.3",
|
|
25731
26903
|
"@tailwindcss/vite": "^4.0.15",
|
|
25732
26904
|
"@tanstack/react-router": "^1.141.1",
|
|
25733
26905
|
"class-variance-authority": "^0.7.1",
|
|
@@ -26128,7 +27300,6 @@ export default defineConfig({
|
|
|
26128
27300
|
"dependencies": {
|
|
26129
27301
|
"@base-ui/react": "^1.0.0",
|
|
26130
27302
|
"shadcn": "^3.6.2",
|
|
26131
|
-
"@tanstack/react-form": "^1.23.5",
|
|
26132
27303
|
"@tailwindcss/vite": "^4.1.8",
|
|
26133
27304
|
"@tanstack/react-query": "^5.80.6",
|
|
26134
27305
|
"@tanstack/react-router": "^1.141.1",
|
|
@@ -27561,7 +28732,6 @@ dist-ssr
|
|
|
27561
28732
|
"dependencies": {
|
|
27562
28733
|
"@tailwindcss/vite": "^4.1.13",
|
|
27563
28734
|
"@tanstack/router-plugin": "^1.131.44",
|
|
27564
|
-
"@tanstack/solid-form": "^1.20.0",
|
|
27565
28735
|
"@tanstack/solid-router": "^1.131.44",
|
|
27566
28736
|
"lucide-solid": "^0.544.0",
|
|
27567
28737
|
"solid-js": "^1.9.9",
|
|
@@ -27883,9 +29053,7 @@ vite.config.ts.timestamp-*
|
|
|
27883
29053
|
"tailwindcss": "^4.1.12",
|
|
27884
29054
|
"vite": "^7.1.2"
|
|
27885
29055
|
},
|
|
27886
|
-
"dependencies": {
|
|
27887
|
-
"@tanstack/svelte-form": "^1.19.2"
|
|
27888
|
-
}
|
|
29056
|
+
"dependencies": {}
|
|
27889
29057
|
}
|
|
27890
29058
|
`],
|
|
27891
29059
|
["frontend/svelte/src/app.css", `@import "tailwindcss";
|