@ramonclaudio/create-vexpo 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/README.md +10 -10
  2. package/dist/index.js +8 -7
  3. package/dist/templates/default/.eas/workflows/asc-events.yml +9 -6
  4. package/dist/templates/default/.eas/workflows/deploy-production.yml +28 -15
  5. package/dist/templates/default/.eas/workflows/e2e-tests.yml +3 -2
  6. package/dist/templates/default/.eas/workflows/pr-preview.yml +12 -21
  7. package/dist/templates/default/.eas/workflows/release.yml +3 -7
  8. package/dist/templates/default/.eas/workflows/rollback.yml +54 -28
  9. package/dist/templates/default/.eas/workflows/rollout.yml +27 -33
  10. package/dist/templates/default/.eas/workflows/rotate-apple-jwt.yml +1 -5
  11. package/dist/templates/default/.eas/workflows/testflight.yml +3 -7
  12. package/dist/templates/default/.github/workflows/check.yml +20 -12
  13. package/dist/templates/default/.maestro/launch.yaml +19 -10
  14. package/dist/templates/default/AGENTS.md +25 -8
  15. package/dist/templates/default/DESIGN.md +14 -10
  16. package/dist/templates/default/README.md +83 -78
  17. package/dist/templates/default/SETUP.md +159 -152
  18. package/dist/templates/default/__tests__/convex/_auth-harness.test.ts +112 -0
  19. package/dist/templates/default/__tests__/convex/_harness.ts +132 -0
  20. package/dist/templates/default/__tests__/convex/appAttest.test.ts +172 -0
  21. package/dist/templates/default/__tests__/convex/appAttestStore.test.ts +48 -0
  22. package/dist/templates/default/__tests__/convex/pushTokens-remove.test.ts +106 -0
  23. package/dist/templates/default/__tests__/convex/pushTokens-upsert.test.ts +146 -0
  24. package/dist/templates/default/__tests__/convex/users-deleteAccount.test.ts +140 -0
  25. package/dist/templates/default/__tests__/convex/users-deleteAvatar.test.ts +104 -0
  26. package/dist/templates/default/__tests__/convex/users-getMe.test.ts +98 -0
  27. package/dist/templates/default/__tests__/convex/users-getUser.test.ts +120 -0
  28. package/dist/templates/default/__tests__/convex/users-hardDeleteExpired.test.ts +67 -0
  29. package/dist/templates/default/__tests__/convex/users-restoreAccount.test.ts +96 -0
  30. package/dist/templates/default/__tests__/convex/users-updateAvatar.test.ts +92 -0
  31. package/dist/templates/default/__tests__/convex/users-updateProfile.test.ts +126 -0
  32. package/dist/templates/default/__tests__/convex/webhook.test.ts +31 -0
  33. package/dist/templates/default/__tests__/lib/deep-link.test.ts +51 -6
  34. package/dist/templates/default/__tests__/lib/schemas.test.ts +205 -0
  35. package/dist/templates/default/__tests__/lib/text-style.test.ts +31 -0
  36. package/dist/templates/default/_env.example +7 -7
  37. package/dist/templates/default/_gitattributes +1 -1
  38. package/dist/templates/default/_gitignore +17 -2
  39. package/dist/templates/default/_npmrc +7 -0
  40. package/dist/templates/default/_oxlintrc.json +1 -1
  41. package/dist/templates/default/app-store/accessibility.config.json +20 -0
  42. package/dist/templates/default/app-store/privacy.config.json +27 -0
  43. package/dist/templates/default/app.config.ts +105 -33
  44. package/dist/templates/default/app.json +1 -9
  45. package/dist/templates/default/convex/_generated/api.d.ts +12 -0
  46. package/dist/templates/default/convex/admin.ts +0 -13
  47. package/dist/templates/default/convex/appAttest.ts +467 -0
  48. package/dist/templates/default/convex/appAttestStore.ts +141 -0
  49. package/dist/templates/default/convex/apple.ts +53 -0
  50. package/dist/templates/default/convex/auth.ts +6 -45
  51. package/dist/templates/default/convex/constants.ts +2 -7
  52. package/dist/templates/default/convex/crons.ts +12 -5
  53. package/dist/templates/default/convex/email.ts +4 -24
  54. package/dist/templates/default/convex/env.ts +0 -4
  55. package/dist/templates/default/convex/errors.ts +0 -7
  56. package/dist/templates/default/convex/functions.ts +0 -26
  57. package/dist/templates/default/convex/http.ts +3 -5
  58. package/dist/templates/default/convex/log.ts +2 -25
  59. package/dist/templates/default/convex/pushSender.ts +145 -0
  60. package/dist/templates/default/convex/pushTokens.ts +110 -13
  61. package/dist/templates/default/convex/rateLimit.ts +8 -39
  62. package/dist/templates/default/convex/schema.ts +48 -5
  63. package/dist/templates/default/convex/tsconfig.json +1 -0
  64. package/dist/templates/default/convex/users.ts +143 -61
  65. package/dist/templates/default/convex/validators.ts +1 -38
  66. package/dist/templates/default/convex/webhook.ts +1 -31
  67. package/dist/templates/default/convex.json +1 -2
  68. package/dist/templates/default/metro.config.js +9 -1
  69. package/dist/templates/default/package.json +67 -70
  70. package/dist/templates/default/plugins/README.md +5 -1
  71. package/dist/templates/default/scripts/README.md +9 -9
  72. package/dist/templates/default/scripts/_run.mjs +3 -20
  73. package/dist/templates/default/scripts/clean.ts +81 -69
  74. package/dist/templates/default/scripts/gen-update-cert.mjs +98 -0
  75. package/dist/templates/default/{app → src/app}/(app)/(tabs)/(home)/index.tsx +21 -6
  76. package/dist/templates/default/{app → src/app}/(app)/(tabs)/(home,search)/_layout.tsx +9 -8
  77. package/dist/templates/default/{app → src/app}/(app)/(tabs)/(search)/index.tsx +26 -24
  78. package/dist/templates/default/{app → src/app}/(app)/(tabs)/_layout.tsx +3 -4
  79. package/dist/templates/default/{app → src/app}/(app)/(tabs)/settings/_layout.tsx +10 -6
  80. package/dist/templates/default/{app → src/app}/(app)/(tabs)/settings/index.tsx +81 -51
  81. package/dist/templates/default/{app → src/app}/(app)/(tabs)/settings/preferences.tsx +72 -12
  82. package/dist/templates/default/src/app/(app)/_layout.tsx +147 -0
  83. package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/_layout.tsx +4 -5
  84. package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/forgot-password.tsx +15 -9
  85. package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/reset-password.tsx +88 -14
  86. package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/sign-in.tsx +65 -35
  87. package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/sign-up.tsx +131 -196
  88. package/dist/templates/default/src/app/(app)/debug.tsx +479 -0
  89. package/dist/templates/default/{app → src/app}/(app)/help.tsx +76 -64
  90. package/dist/templates/default/{app → src/app}/(app)/linked.tsx +21 -27
  91. package/dist/templates/default/{app → src/app}/(app)/privacy.tsx +35 -8
  92. package/dist/templates/default/src/app/(app)/profile/change-password.tsx +264 -0
  93. package/dist/templates/default/{app/(app)/profile.tsx → src/app/(app)/profile/index.tsx} +179 -255
  94. package/dist/templates/default/src/app/(app)/restore-account.tsx +192 -0
  95. package/dist/templates/default/src/app/(app)/sessions.tsx +287 -0
  96. package/dist/templates/default/src/app/(app)/welcome.tsx +194 -0
  97. package/dist/templates/default/src/app/+native-intent.tsx +25 -0
  98. package/dist/templates/default/src/app/+not-found.tsx +43 -0
  99. package/dist/templates/default/{app → src/app}/_layout.tsx +28 -37
  100. package/dist/templates/default/src/components/auth/apple-button.tsx +51 -0
  101. package/dist/templates/default/{components → src/components}/auth/otp-verification.tsx +79 -58
  102. package/dist/templates/default/{components → src/components}/auth/password-field.tsx +74 -18
  103. package/dist/templates/default/src/components/auth/segmented-toggle.tsx +71 -0
  104. package/dist/templates/default/src/components/ui/content-unavailable.tsx +81 -0
  105. package/dist/templates/default/src/components/ui/convex-error.tsx +21 -0
  106. package/dist/templates/default/src/components/ui/error-boundary.tsx +89 -0
  107. package/dist/templates/default/{components → src/components}/ui/loading-screen.tsx +5 -4
  108. package/dist/templates/default/{components → src/components}/ui/material.tsx +50 -17
  109. package/dist/templates/default/src/components/ui/offline-banner.tsx +59 -0
  110. package/dist/templates/default/{components → src/components}/ui/prominent-button.tsx +8 -11
  111. package/dist/templates/default/{components → src/components}/ui/skeleton.tsx +31 -13
  112. package/dist/templates/default/src/components/ui/status-text.tsx +64 -0
  113. package/dist/templates/default/src/components/ui/update-banner.tsx +85 -0
  114. package/dist/templates/default/{constants → src/constants}/layout.ts +0 -6
  115. package/dist/templates/default/{constants → src/constants}/theme.ts +49 -64
  116. package/dist/templates/default/{constants → src/constants}/ui.ts +13 -4
  117. package/dist/templates/default/src/hooks/use-debounce.ts +12 -0
  118. package/dist/templates/default/src/hooks/use-deep-link.ts +51 -0
  119. package/dist/templates/default/src/hooks/use-delete-account.ts +35 -0
  120. package/dist/templates/default/src/hooks/use-motion-screen-options.ts +13 -0
  121. package/dist/templates/default/src/hooks/use-network.ts +34 -0
  122. package/dist/templates/default/{hooks → src/hooks}/use-notifications.ts +39 -30
  123. package/dist/templates/default/src/hooks/use-reduce-transparency.ts +30 -0
  124. package/dist/templates/default/{hooks → src/hooks}/use-theme.ts +0 -5
  125. package/dist/templates/default/src/lib/appAttest.ts +78 -0
  126. package/dist/templates/default/src/lib/assets.ts +9 -0
  127. package/dist/templates/default/src/lib/deep-link.ts +82 -0
  128. package/dist/templates/default/{lib → src/lib}/dev-menu.ts +0 -4
  129. package/dist/templates/default/{lib → src/lib}/device.ts +1 -13
  130. package/dist/templates/default/{lib → src/lib}/dynamic-font.ts +13 -10
  131. package/dist/templates/default/src/lib/dynamic-symbol-size.ts +33 -0
  132. package/dist/templates/default/src/lib/masks.ts +21 -0
  133. package/dist/templates/default/src/lib/native-state.ts +20 -0
  134. package/dist/templates/default/{lib → src/lib}/notifications.ts +7 -45
  135. package/dist/templates/default/{lib → src/lib}/preferences.ts +0 -2
  136. package/dist/templates/default/{lib → src/lib}/schemas.ts +19 -16
  137. package/dist/templates/default/src/lib/text-style.ts +20 -0
  138. package/dist/templates/default/{lib → src/lib}/updates.ts +0 -7
  139. package/dist/templates/default/store.config.json +1 -1
  140. package/dist/templates/default/tsconfig.json +3 -1
  141. package/dist/templates/default/vitest.config.ts +8 -1
  142. package/package.json +5 -5
  143. package/dist/templates/default/app/(app)/_layout.tsx +0 -73
  144. package/dist/templates/default/app/(app)/debug.tsx +0 -389
  145. package/dist/templates/default/app/(app)/sessions.tsx +0 -191
  146. package/dist/templates/default/app/(app)/welcome.tsx +0 -140
  147. package/dist/templates/default/app/+native-intent.tsx +0 -14
  148. package/dist/templates/default/app/+not-found.tsx +0 -51
  149. package/dist/templates/default/bun.lock +0 -1860
  150. package/dist/templates/default/components/auth/segmented-toggle.tsx +0 -47
  151. package/dist/templates/default/components/ui/convex-error.tsx +0 -32
  152. package/dist/templates/default/components/ui/error-boundary.tsx +0 -57
  153. package/dist/templates/default/components/ui/offline-banner.tsx +0 -58
  154. package/dist/templates/default/components/ui/status-text.tsx +0 -49
  155. package/dist/templates/default/components/ui/update-banner.tsx +0 -82
  156. package/dist/templates/default/fingerprint.config.js +0 -9
  157. package/dist/templates/default/hooks/use-debounce.ts +0 -20
  158. package/dist/templates/default/hooks/use-deep-link.ts +0 -43
  159. package/dist/templates/default/hooks/use-network.ts +0 -11
  160. package/dist/templates/default/lib/assets.ts +0 -17
  161. package/dist/templates/default/lib/deep-link.ts +0 -71
  162. package/dist/templates/default/patches/PR-368.patch +0 -91
  163. package/dist/templates/default/patches/convex-dev-better-auth-0.12.2.tgz +0 -0
  164. /package/dist/templates/default/{hooks → src/hooks}/use-navigation-tracking.ts +0 -0
  165. /package/dist/templates/default/{hooks → src/hooks}/use-onboarding.ts +0 -0
  166. /package/dist/templates/default/{hooks → src/hooks}/use-reduced-motion.ts +0 -0
  167. /package/dist/templates/default/{hooks → src/hooks}/use-updates.ts +0 -0
  168. /package/dist/templates/default/{lib → src/lib}/a11y.ts +0 -0
  169. /package/dist/templates/default/{lib → src/lib}/app.ts +0 -0
  170. /package/dist/templates/default/{lib → src/lib}/auth-client.ts +0 -0
  171. /package/dist/templates/default/{lib → src/lib}/convex-auth.tsx +0 -0
  172. /package/dist/templates/default/{lib → src/lib}/env.ts +0 -0
  173. /package/dist/templates/default/{lib → src/lib}/haptics.ts +0 -0
  174. /package/dist/templates/default/{lib → src/lib}/storage.ts +0 -0
@@ -3,17 +3,29 @@ import * as AppleAuthentication from "expo-apple-authentication";
3
3
  import { Image as ExpoImage } from "expo-image";
4
4
  import { router } from "expo-router";
5
5
  import { useQuery } from "convex/react";
6
- import { Host, ScrollView, VStack, TextField, Button, Text, RNHostView } from "@expo/ui/swift-ui";
6
+ import {
7
+ Host,
8
+ ScrollView,
9
+ VStack,
10
+ TextField,
11
+ Button,
12
+ Text,
13
+ RNHostView,
14
+ useNativeState,
15
+ } from "@expo/ui/swift-ui";
16
+ import { runOnJS } from "react-native-worklets";
7
17
  import {
8
18
  autocorrectionDisabled,
9
19
  foregroundStyle,
10
20
  buttonStyle,
11
21
  background,
12
22
  clipShape,
23
+ defaultScrollAnchorForRole,
13
24
  disabled,
14
25
  keyboardType,
15
26
  onSubmit as onSubmitModifier,
16
27
  submitLabel,
28
+ textContentType,
17
29
  textFieldStyle,
18
30
  textInputAutocapitalization,
19
31
  padding,
@@ -30,6 +42,7 @@ import { api } from "@/convex/_generated/api";
30
42
  import { authClient } from "@/lib/auth-client";
31
43
  import { assets } from "@/lib/assets";
32
44
  import { haptics } from "@/lib/haptics";
45
+ import { maskUsername } from "@/lib/masks";
33
46
  import {
34
47
  firstError,
35
48
  forgotPasswordSchema,
@@ -42,7 +55,8 @@ import { SegmentedToggle } from "@/components/auth/segmented-toggle";
42
55
  import { ProminentButton } from "@/components/ui/prominent-button";
43
56
  import { ErrorText } from "@/components/ui/status-text";
44
57
  import { announce } from "@/lib/a11y";
45
- import { useColorScheme, useColors, useThemedAsset } from "@/hooks/use-theme";
58
+ import { useColors, useThemedAsset } from "@/hooks/use-theme";
59
+ import { AppleButton } from "@/components/auth/apple-button";
46
60
 
47
61
  type SignInState = { error?: string; ok?: boolean };
48
62
  const initialState: SignInState = {};
@@ -51,12 +65,12 @@ type SignInMethod = "email" | "username" | "otp";
51
65
 
52
66
  export default function SignInScreen() {
53
67
  const dfont = useDynamicFont();
54
- const colorScheme = useColorScheme();
55
68
  const colors = useColors();
56
69
  const brandIcon = useThemedAsset(assets.brandIconLight, assets.brandIconDark);
57
70
 
58
71
  const [signInMethod, setSignInMethod] = useState<SignInMethod>("email");
59
72
  const [emailValue, setEmailValue] = useState("");
73
+ const usernameFieldState = useNativeState("");
60
74
  const [usernameValue, setUsernameValue] = useState("");
61
75
  const [password, setPassword] = useState("");
62
76
  const [otpEmail, setOtpEmail] = useState("");
@@ -65,7 +79,7 @@ export default function SignInScreen() {
65
79
  const providers = useQuery(api.auth.getEnabledProviders);
66
80
  const showApple = appleAvailable && providers?.apple === true;
67
81
  // Email features (OTP sign-in, password reset) require the Convex env to
68
- // have `REQUIRE_EMAIL_VERIFICATION=true` (set by `bunx vexpo full`).
82
+ // have `REQUIRE_EMAIL_VERIFICATION=true` (set by `npx vexpo full`).
69
83
  // Until then, hide them so users don't hit a code-was-logged-to-console
70
84
  // dead end. Email + password sign-up/sign-in remains available.
71
85
  const emailFeatures = providers?.emailFeatures === true;
@@ -234,16 +248,24 @@ export default function SignInScreen() {
234
248
  const inputModifiers = [
235
249
  textFieldStyle("plain"),
236
250
  padding({ horizontal: 16 }),
237
- frame({ maxWidth: Infinity, height: ButtonTokens.height }),
251
+ frame({ maxWidth: Infinity, minHeight: ButtonTokens.height }),
238
252
  background(colors.muted as string),
239
253
  clipShape("capsule"),
240
254
  dfont({ size: 16 }),
241
255
  ];
242
256
 
243
257
  return (
244
- <Host style={{ flex: 1, backgroundColor: colors.background }}>
258
+ <Host testID="sign-in-screen" style={{ flex: 1, backgroundColor: colors.background }}>
245
259
  <ScrollView
246
- modifiers={[scrollDismissesKeyboard("interactively"), tint(colors.primary as string)]}
260
+ modifiers={[
261
+ scrollDismissesKeyboard("interactively"),
262
+ tint(colors.primary as string),
263
+ // Swapping the method toggle hides or shows a whole field group, the
264
+ // biggest single-tap size change on this screen. Pin the visible
265
+ // center so the user does not lose the field they were aiming for.
266
+ // No-op below iOS 18. Ships via upstream expo/expo#43923.
267
+ defaultScrollAnchorForRole("center", "sizeChanges"),
268
+ ]}
247
269
  >
248
270
  <VStack
249
271
  spacing={20}
@@ -260,7 +282,9 @@ export default function SignInScreen() {
260
282
  </RNHostView>
261
283
 
262
284
  <VStack spacing={6} alignment="leading">
263
- <Text modifiers={[dfont({ size: 28, weight: "bold" })]}>Sign in</Text>
285
+ <Text testID="sign-in-title" modifiers={[dfont({ size: 28, weight: "bold" })]}>
286
+ Sign in
287
+ </Text>
264
288
  <Text
265
289
  modifiers={[dfont({ size: 16 }), foregroundStyle(colors.mutedForeground as string)]}
266
290
  >
@@ -271,17 +295,21 @@ export default function SignInScreen() {
271
295
  </VStack>
272
296
 
273
297
  <SegmentedToggle
298
+ testID="sign-in-auth-mode"
299
+ accessibilityLabel="Sign in or sign up"
274
300
  value="sign-in"
275
301
  options={[
276
302
  { value: "sign-in", label: "Sign in" },
277
303
  { value: "sign-up", label: "Sign up" },
278
304
  ]}
279
305
  onChange={(v) => {
280
- if (v === "sign-up") router.replace("/sign-up");
306
+ if (v === "sign-up") router.replace("/auth/sign-up");
281
307
  }}
282
308
  />
283
309
 
284
310
  <SegmentedToggle
311
+ testID="sign-in-method"
312
+ accessibilityLabel="Sign-in method"
285
313
  value={signInMethod}
286
314
  options={
287
315
  emailFeatures
@@ -298,20 +326,23 @@ export default function SignInScreen() {
298
326
  onChange={(value) => setSignInMethod(value as SignInMethod)}
299
327
  />
300
328
 
301
- {error && <ErrorText>{error}</ErrorText>}
329
+ {error && <ErrorText testID="sign-in-error">{error}</ErrorText>}
302
330
 
303
331
  {signInMethod === "email" && (
304
332
  <>
305
333
  <VStack spacing={6} alignment="leading" modifiers={[frame({ maxWidth: Infinity })]}>
306
334
  <Text modifiers={labelModifiers}>Email</Text>
307
335
  <TextField
336
+ testID="sign-in-email"
308
337
  placeholder="you@example.com"
309
338
  onTextChange={setEmailValue}
310
339
  modifiers={[
311
340
  ...inputModifiers,
312
341
  keyboardType("email-address"),
313
342
  autocorrectionDisabled(),
343
+ // upstream expo/expo#44547 + #44548: keyboard shift + autofill semantics
314
344
  textInputAutocapitalization("never"),
345
+ textContentType("username"),
315
346
  disabled(isLoading),
316
347
  submitLabel("next"),
317
348
  accessibilityLabel("Email address"),
@@ -322,6 +353,7 @@ export default function SignInScreen() {
322
353
  <VStack spacing={6} alignment="leading" modifiers={[frame({ maxWidth: Infinity })]}>
323
354
  <Text modifiers={labelModifiers}>Password</Text>
324
355
  <PasswordField
356
+ testID="sign-in-email-password"
325
357
  onTextChange={setPassword}
326
358
  onSubmit={() => startTransition(() => signInWithEmail())}
327
359
  disabled={isLoading}
@@ -331,6 +363,7 @@ export default function SignInScreen() {
331
363
  </VStack>
332
364
  {emailFeatures && (
333
365
  <Button
366
+ testID="sign-in-email-forgot-password"
334
367
  label="Forgot password?"
335
368
  modifiers={[
336
369
  buttonStyle("plain"),
@@ -339,7 +372,7 @@ export default function SignInScreen() {
339
372
  ]}
340
373
  onPress={() => {
341
374
  haptics.light();
342
- router.push("/forgot-password");
375
+ router.push("/auth/forgot-password");
343
376
  }}
344
377
  />
345
378
  )}
@@ -351,13 +384,21 @@ export default function SignInScreen() {
351
384
  <VStack spacing={6} alignment="leading" modifiers={[frame({ maxWidth: Infinity })]}>
352
385
  <Text modifiers={labelModifiers}>Username</Text>
353
386
  <TextField
387
+ testID="sign-in-username"
388
+ text={usernameFieldState}
354
389
  placeholder="johndoe"
355
- onTextChange={setUsernameValue}
390
+ onTextChange={(text) => {
391
+ "worklet";
392
+ const next = maskUsername(text);
393
+ usernameFieldState.value = next;
394
+ runOnJS(setUsernameValue)(next);
395
+ }}
356
396
  modifiers={[
357
397
  ...inputModifiers,
358
398
  keyboardType("ascii-capable"),
359
399
  autocorrectionDisabled(),
360
400
  textInputAutocapitalization("never"),
401
+ textContentType("username"),
361
402
  disabled(isLoading),
362
403
  submitLabel("next"),
363
404
  accessibilityLabel("Username"),
@@ -368,6 +409,7 @@ export default function SignInScreen() {
368
409
  <VStack spacing={6} alignment="leading" modifiers={[frame({ maxWidth: Infinity })]}>
369
410
  <Text modifiers={labelModifiers}>Password</Text>
370
411
  <PasswordField
412
+ testID="sign-in-username-password"
371
413
  onTextChange={setPassword}
372
414
  onSubmit={() => startTransition(() => signInWithUsername())}
373
415
  disabled={isLoading}
@@ -377,6 +419,7 @@ export default function SignInScreen() {
377
419
  </VStack>
378
420
  {emailFeatures && (
379
421
  <Button
422
+ testID="sign-in-username-forgot-password"
380
423
  label="Forgot password?"
381
424
  modifiers={[
382
425
  buttonStyle("plain"),
@@ -385,7 +428,7 @@ export default function SignInScreen() {
385
428
  ]}
386
429
  onPress={() => {
387
430
  haptics.light();
388
- router.push("/forgot-password");
431
+ router.push("/auth/forgot-password");
389
432
  }}
390
433
  />
391
434
  )}
@@ -396,6 +439,7 @@ export default function SignInScreen() {
396
439
  <VStack spacing={6} alignment="leading" modifiers={[frame({ maxWidth: Infinity })]}>
397
440
  <Text modifiers={labelModifiers}>Email</Text>
398
441
  <TextField
442
+ testID="sign-in-otp-email"
399
443
  placeholder="you@example.com"
400
444
  onTextChange={setOtpEmail}
401
445
  modifiers={[
@@ -403,6 +447,7 @@ export default function SignInScreen() {
403
447
  keyboardType("email-address"),
404
448
  autocorrectionDisabled(),
405
449
  textInputAutocapitalization("never"),
450
+ textContentType("username"),
406
451
  onSubmitModifier(() => startTransition(() => sendSignInOtp())),
407
452
  disabled(isLoading),
408
453
  submitLabel("send"),
@@ -417,34 +462,19 @@ export default function SignInScreen() {
417
462
  )}
418
463
 
419
464
  <ProminentButton
465
+ testID="sign-in-submit"
420
466
  label={primaryLabel}
421
467
  onPress={() => startTransition(onSubmit)}
422
468
  disabled={isLoading}
423
469
  />
424
470
 
425
471
  {!isOtp && showApple && (
426
- <VStack
427
- alignment="center"
428
- modifiers={[frame({ maxWidth: Infinity, height: ButtonTokens.height })]}
429
- >
430
- <RNHostView>
431
- <AppleAuthentication.AppleAuthenticationButton
432
- buttonType={AppleAuthentication.AppleAuthenticationButtonType.SIGN_IN}
433
- buttonStyle={
434
- colorScheme === "dark"
435
- ? AppleAuthentication.AppleAuthenticationButtonStyle.WHITE
436
- : AppleAuthentication.AppleAuthenticationButtonStyle.BLACK
437
- }
438
- cornerRadius={ButtonTokens.cornerRadius}
439
- style={{
440
- width: "100%",
441
- height: "100%",
442
- opacity: isLoading ? 0.5 : 1,
443
- }}
444
- onPress={() => startTransition(() => signInWithApple())}
445
- />
446
- </RNHostView>
447
- </VStack>
472
+ <AppleButton
473
+ testID="sign-in-apple"
474
+ type={AppleAuthentication.AppleAuthenticationButtonType.SIGN_IN}
475
+ onPress={() => startTransition(() => signInWithApple())}
476
+ disabled={isLoading}
477
+ />
448
478
  )}
449
479
  </VStack>
450
480
  </ScrollView>