@vvedantb/native-ui 0.17.2 → 0.18.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.
Files changed (220) hide show
  1. package/dist/AlertDialog.jsx +123 -0
  2. package/dist/AmbientBackground.jsx +73 -0
  3. package/dist/ArtifactList.jsx +59 -0
  4. package/dist/Avatar.jsx +285 -0
  5. package/dist/Badge.jsx +154 -0
  6. package/dist/BottomSheetContainer.jsx +208 -0
  7. package/dist/BulkActionBar.jsx +717 -0
  8. package/dist/Button.jsx +344 -0
  9. package/dist/CaptureButton.jsx +104 -0
  10. package/dist/Card.jsx +371 -0
  11. package/dist/Checkbox.jsx +177 -0
  12. package/dist/Chip.jsx +79 -0
  13. package/dist/ColorSwatches.jsx +159 -0
  14. package/dist/ConfirmDialog.jsx +157 -0
  15. package/dist/DateTimeField.jsx +204 -0
  16. package/dist/Dialog.jsx +614 -0
  17. package/dist/DividedList.jsx +120 -0
  18. package/dist/DropdownMenu.jsx +849 -0
  19. package/dist/EmptyState.jsx +434 -0
  20. package/dist/ExpoWebRoot.jsx +134 -0
  21. package/dist/FloatingFAB.jsx +95 -0
  22. package/dist/FloatingSurface.jsx +226 -0
  23. package/dist/FloatingTabBar.jsx +403 -0
  24. package/dist/HeaderIconButton.jsx +53 -0
  25. package/dist/IconButton.jsx +157 -0
  26. package/dist/Input.jsx +96 -0
  27. package/dist/Label.jsx +96 -0
  28. package/dist/LabeledSwitchRow.jsx +170 -0
  29. package/dist/LiquidGlass.jsx +2 -0
  30. package/dist/ListRow.jsx +281 -0
  31. package/dist/LoadingState.jsx +205 -0
  32. package/dist/PageIndicator.jsx +128 -0
  33. package/dist/PageTitle.jsx +205 -0
  34. package/dist/Pager.jsx +105 -0
  35. package/dist/PasswordInput.jsx +120 -0
  36. package/dist/PillTabs.jsx +300 -0
  37. package/dist/PinnedBotTile.jsx +212 -0
  38. package/dist/Progress.jsx +147 -0
  39. package/dist/SafeAreaContainer.jsx +38 -0
  40. package/dist/ScreenContainer.jsx +117 -0
  41. package/dist/SearchInput.jsx +150 -0
  42. package/dist/SectionLabel.jsx +128 -0
  43. package/dist/Select.jsx +223 -0
  44. package/dist/Separator.jsx +95 -0
  45. package/dist/Sheet.jsx +343 -0
  46. package/dist/SheetRow.jsx +198 -0
  47. package/dist/Skeleton.jsx +311 -0
  48. package/dist/Spinner.jsx +195 -0
  49. package/dist/StackPageHeader.jsx +160 -0
  50. package/dist/StatGrid.jsx +74 -0
  51. package/dist/StatusDot.jsx +129 -0
  52. package/dist/SurfaceRow.jsx +143 -0
  53. package/dist/SwipeableRow.jsx +50 -0
  54. package/dist/Switch.jsx +130 -0
  55. package/dist/Tabs.jsx +385 -0
  56. package/dist/Textarea.jsx +103 -0
  57. package/dist/ThemeToggle.jsx +78 -0
  58. package/dist/Toast.jsx +396 -0
  59. package/dist/ai/activity-step-detail.jsx +179 -0
  60. package/dist/ai/activity-step-label.js +816 -0
  61. package/dist/ai/activity-step-types.js +14 -0
  62. package/dist/ai/activity-step.jsx +98 -0
  63. package/dist/ai/activity-tasks-utils.js +172 -0
  64. package/dist/ai/activity-timeline.js +47 -0
  65. package/dist/ai/attachments.jsx +258 -0
  66. package/dist/ai/card-button.jsx +188 -0
  67. package/dist/ai/chain-of-thought.jsx +459 -0
  68. package/dist/ai/chat-jump-rail.jsx +57 -0
  69. package/dist/ai/chat-text.js +44 -0
  70. package/dist/ai/chat-thread.jsx +135 -0
  71. package/dist/ai/choice-question.jsx +925 -0
  72. package/dist/ai/code-block.jsx +574 -0
  73. package/dist/ai/collapsible.jsx +275 -0
  74. package/dist/ai/conversation.jsx +723 -0
  75. package/dist/ai/file-chip.jsx +198 -0
  76. package/dist/ai/icon-button.jsx +179 -0
  77. package/dist/ai/index.js +37 -0
  78. package/dist/ai/message-action-sheet.jsx +408 -0
  79. package/dist/ai/message-quote.jsx +241 -0
  80. package/dist/ai/message.jsx +739 -0
  81. package/dist/ai/prompt-input-menu.jsx +365 -0
  82. package/dist/ai/prompt-input.jsx +626 -0
  83. package/dist/ai/reasoning.jsx +446 -0
  84. package/dist/ai/reply-quote-chip.jsx +278 -0
  85. package/dist/ai/response-text.jsx +130 -0
  86. package/dist/ai/shimmer.jsx +149 -0
  87. package/dist/ai/sources.jsx +260 -0
  88. package/dist/ai/subagent-cards.jsx +645 -0
  89. package/dist/ai/suggestion.jsx +186 -0
  90. package/dist/ai/tool-approval-card.jsx +359 -0
  91. package/dist/ai/trace.jsx +728 -0
  92. package/dist/ai/unified-diff.jsx +280 -0
  93. package/dist/ai/use-streaming-disclosure.js +70 -0
  94. package/dist/auth/AuthProviders.jsx +27 -0
  95. package/dist/auth/AuthScreen.jsx +292 -0
  96. package/dist/auth/GoogleSignInButton.jsx +188 -0
  97. package/dist/auth/SecondFactorScreen.jsx +133 -0
  98. package/dist/auth/SignInScreen.jsx +482 -0
  99. package/dist/auth/authErrors.js +131 -0
  100. package/dist/auth/index.js +11 -0
  101. package/dist/auth/totp.js +378 -0
  102. package/dist/blur.js +36 -0
  103. package/dist/brand/AppIcons.jsx +118 -0
  104. package/dist/brand/AppMark.jsx +128 -0
  105. package/dist/brand/AppWordmark.jsx +138 -0
  106. package/dist/brand/GoogleMark.jsx +71 -0
  107. package/dist/brand/ProviderIcon.jsx +192 -0
  108. package/dist/brand/familyApps.js +33 -0
  109. package/dist/brand/index.js +11 -0
  110. package/dist/brand/svg.js +252 -0
  111. package/dist/brand/vProAppBenefits.js +19 -0
  112. package/dist/cn.js +5 -0
  113. package/dist/colors.js +15 -0
  114. package/dist/configureTheme.js +72 -0
  115. package/dist/controlStyles.js +520 -0
  116. package/dist/definiteSize.js +12 -0
  117. package/dist/dialogLayout.js +18 -0
  118. package/dist/entries/ai.js +4 -0
  119. package/dist/entries/ambient.js +3 -0
  120. package/dist/entries/auth.js +5 -0
  121. package/dist/entries/bottom-sheet.js +2 -0
  122. package/dist/entries/brand.js +2 -0
  123. package/dist/entries/button.js +2 -0
  124. package/dist/entries/date-time.js +3 -0
  125. package/dist/entries/expo-router.js +4 -0
  126. package/dist/entries/expo-web-root.js +3 -0
  127. package/dist/entries/floating.js +7 -0
  128. package/dist/entries/gesture-handler.js +4 -0
  129. package/dist/entries/keyboard-controller.js +4 -0
  130. package/dist/entries/layout.js +16 -0
  131. package/dist/entries/markdown.js +4 -0
  132. package/dist/entries/marketing.js +3 -0
  133. package/dist/entries/page-title.js +7 -0
  134. package/dist/entries/password-input.js +3 -0
  135. package/dist/entries/primitives.js +57 -0
  136. package/dist/entries/search-input.js +3 -0
  137. package/dist/entries/tab-bar.js +4 -0
  138. package/dist/entries/theme.js +7 -0
  139. package/dist/entries/toast.js +5 -0
  140. package/dist/entries/video.js +3 -0
  141. package/dist/entries/webview.js +4 -0
  142. package/dist/examples/AiChatGallery.jsx +509 -0
  143. package/dist/examples/ParityGallery.jsx +1307 -0
  144. package/dist/floating.js +62 -0
  145. package/dist/glassBlur.jsx +145 -0
  146. package/dist/gradient.js +17 -0
  147. package/dist/haptics.js +44 -0
  148. package/dist/highlight.js +382 -0
  149. package/dist/index.js +85 -0
  150. package/dist/initials.js +4 -0
  151. package/dist/loadingGrid.js +81 -0
  152. package/dist/markdown/Markdown.jsx +515 -0
  153. package/dist/markdown/MarkdownTable.jsx +443 -0
  154. package/dist/markdown/tableText.js +68 -0
  155. package/dist/marketing/PricingModal.jsx +608 -0
  156. package/dist/marketing/UpgradeToProCard.jsx +164 -0
  157. package/dist/marketing/pricing.js +34 -0
  158. package/dist/media/ImageGallery.jsx +580 -0
  159. package/dist/media/MediaCard.jsx +162 -0
  160. package/dist/media/imageGrid.js +54 -0
  161. package/dist/motion.js +33 -0
  162. package/dist/pagerSwipe.js +104 -0
  163. package/dist/palette.js +104 -0
  164. package/dist/primitives/button.jsx +127 -0
  165. package/dist/primitives/card.jsx +249 -0
  166. package/dist/primitives/icon.jsx +37 -0
  167. package/dist/primitives/input.jsx +56 -0
  168. package/dist/primitives/keyboard-controller.jsx +207 -0
  169. package/dist/primitives/keyboard-parts.jsx +75 -0
  170. package/dist/primitives/native-only-animated-view.jsx +68 -0
  171. package/dist/primitives/overlay.jsx +391 -0
  172. package/dist/primitives/pager-gesture.jsx +131 -0
  173. package/dist/primitives/select-host.jsx +103 -0
  174. package/dist/primitives/select.jsx +555 -0
  175. package/dist/primitives/sheet-drag.jsx +420 -0
  176. package/dist/primitives/sheet-gesture.jsx +219 -0
  177. package/dist/primitives/swipe-row-gesture.jsx +341 -0
  178. package/dist/primitives/switch.jsx +105 -0
  179. package/dist/primitives/text.jsx +145 -0
  180. package/dist/primitives/toast-drag-gesture.jsx +94 -0
  181. package/dist/primitives/use-controllable-state.js +40 -0
  182. package/dist/primitives/web-click-guard.js +69 -0
  183. package/dist/push.js +166 -0
  184. package/dist/relativeTime.js +70 -0
  185. package/dist/router/RouterStackPageHeader.jsx +53 -0
  186. package/dist/selectA11y.js +32 -0
  187. package/dist/settings/MoreFromProjectV.jsx +168 -0
  188. package/dist/settings/SettingsEmptyState.jsx +102 -0
  189. package/dist/settings/SettingsField.jsx +49 -0
  190. package/dist/settings/SettingsSection.jsx +101 -0
  191. package/dist/settings/SettingsStack.jsx +17 -0
  192. package/dist/settings/SettingsToggleRow.jsx +163 -0
  193. package/dist/settings/projectVMobileApps.js +126 -0
  194. package/dist/settings/relaxedSizeStyle.js +11 -0
  195. package/dist/sheetDrag.js +76 -0
  196. package/dist/svgA11y.js +46 -0
  197. package/dist/swipeRow.js +161 -0
  198. package/dist/tabBarRoutes.js +41 -0
  199. package/dist/tabIndicator.js +139 -0
  200. package/dist/tailwind-preset.cjs +255 -0
  201. package/dist/tailwind-preset.d.cts +55 -0
  202. package/dist/theme.jsx +62 -0
  203. package/dist/toastChrome.js +110 -0
  204. package/dist/toastDrag.js +51 -0
  205. package/dist/toastStore.js +54 -0
  206. package/dist/tokens.js +70 -0
  207. package/dist/typography.js +40 -0
  208. package/dist/useReduceMotion.js +32 -0
  209. package/dist/useReduceTransparency.js +27 -0
  210. package/dist/useScheme.js +15 -0
  211. package/dist/video/InlineVideo.jsx +115 -0
  212. package/dist/webview/HtmlView.jsx +169 -0
  213. package/dist/webview/HtmlView.web.jsx +63 -0
  214. package/dist/webview/htmlView.js +43 -0
  215. package/package.json +70 -33
  216. package/src/DropdownMenu.tsx +28 -16
  217. package/src/FloatingTabBar.tsx +78 -41
  218. package/src/ai/conversation.tsx +12 -8
  219. package/src/glassBlur.tsx +47 -22
  220. package/src/primitives/swipe-row-gesture.tsx +11 -2
@@ -0,0 +1,482 @@
1
+ import { c as _c } from "react/compiler-runtime";
2
+ import { useRef, useState } from "react";
3
+ import { Text, View } from "react-native";
4
+ import { useSignIn, useSignUp } from "@clerk/clerk-expo";
5
+ import { useRouter } from "expo-router";
6
+ import { Button } from "../Button";
7
+ import { textStyle } from "../controlStyles";
8
+ import { Input } from "../Input";
9
+ import { PasswordInput } from "../PasswordInput";
10
+ import { toast } from "../toastStore";
11
+ import { useScheme } from "../useScheme";
12
+ import { AuthScreen, FieldError, OrDivider } from "./AuthScreen";
13
+ import { authErrorMessage, DEFAULT_STAGE_TIMEOUT_MS, isAuthTimeoutError, logAuthFailure, withAuthTimeout } from "./authErrors";
14
+ import { SecondFactorScreen } from "./SecondFactorScreen";
15
+ import { pickFirstFactorStrategy, pickSecondFactorStrategy, prepareFirstFactorIfNeeded, submitFirstFactor, supportedFirstFactorStrategies, supportedSecondFactorStrategies, useSecondFactor } from "./totp";
16
+ import { GoogleSignInButton } from "./GoogleSignInButton";
17
+ /** The messages the screen can show. Generic: none names a provider. */
18
+ export const AUTH_MESSAGES = {
19
+ missingFields: "Enter your email and password",
20
+ signInFailed: "Sign in failed",
21
+ invalidCredentials: "Invalid email or password",
22
+ signUpFailed: "Could not create account",
23
+ verifyFailed: "Invalid code",
24
+ missingCode: "Enter the code from the email",
25
+ needsAnotherStep: "This account needs another step to sign in. Finish it on the web.",
26
+ secondFactorTitle: "Two-step verification",
27
+ secondFactorDescription: "Enter the code from your authenticator app.",
28
+ emailCodeTitle: "Check your email",
29
+ emailCodeDescription: "Enter the code we sent to your email.",
30
+ phoneCodeTitle: "Check your phone",
31
+ phoneCodeDescription: "Enter the code we sent by text message.",
32
+ backupCodeTitle: "Enter a backup code",
33
+ backupCodeDescription: "Use one of the backup codes you saved when you set up two-step verification.",
34
+ clientTrustTitle: "Verify it's you",
35
+ clientTrustEmailDescription: "This device is new. Enter the code we sent to your email.",
36
+ clientTrustPhoneDescription: "This device is new. Enter the code we sent by text message.",
37
+ useBackupCode: "Use a backup code",
38
+ useAuthenticator: "Use authenticator app",
39
+ useEmailCode: "Email me a code",
40
+ usePhoneCode: "Text me a code",
41
+ newPasswordTitle: "Set a new password",
42
+ newPasswordDescription: "Your password must change before you can sign in.",
43
+ newPasswordLabel: "New password",
44
+ confirmPasswordLabel: "Confirm new password",
45
+ passwordMismatch: "The passwords do not match",
46
+ passwordTooShort: "Use a longer password",
47
+ resetPasswordFailed: "Could not set the new password",
48
+ setPassword: "Set password"
49
+ };
50
+
51
+ /**
52
+ * A sign-in that is not complete, as `signIn.create` returns it. Passed to
53
+ * `onIncomplete` so the app can run the step itself.
54
+ */
55
+
56
+ const METHOD_LABEL = {
57
+ totp: AUTH_MESSAGES.useAuthenticator,
58
+ email_code: AUTH_MESSAGES.useEmailCode,
59
+ phone_code: AUTH_MESSAGES.usePhoneCode,
60
+ backup_code: AUTH_MESSAGES.useBackupCode
61
+ };
62
+ function secondFactorCopy(strategy, clientTrust) {
63
+ if (strategy === "email_code") {
64
+ return clientTrust ? {
65
+ title: AUTH_MESSAGES.clientTrustTitle,
66
+ description: AUTH_MESSAGES.clientTrustEmailDescription
67
+ } : {
68
+ title: AUTH_MESSAGES.emailCodeTitle,
69
+ description: AUTH_MESSAGES.emailCodeDescription
70
+ };
71
+ }
72
+ if (strategy === "phone_code") {
73
+ return clientTrust ? {
74
+ title: AUTH_MESSAGES.clientTrustTitle,
75
+ description: AUTH_MESSAGES.clientTrustPhoneDescription
76
+ } : {
77
+ title: AUTH_MESSAGES.phoneCodeTitle,
78
+ description: AUTH_MESSAGES.phoneCodeDescription
79
+ };
80
+ }
81
+ if (strategy === "backup_code") {
82
+ return {
83
+ title: AUTH_MESSAGES.backupCodeTitle,
84
+ description: AUTH_MESSAGES.backupCodeDescription
85
+ };
86
+ }
87
+ return {
88
+ title: AUTH_MESSAGES.secondFactorTitle,
89
+ description: AUTH_MESSAGES.secondFactorDescription
90
+ };
91
+ }
92
+ /**
93
+ * Email and password, and Google, sign-in or sign-up. Sign-up sends an email
94
+ * code and checks it before the session starts. On success it calls
95
+ * `onSuccess`, or `router.replace(afterAuthHref, replaceOptions)`.
96
+ *
97
+ * Forgot-password is not here: an app that needs it keeps its own screen.
98
+ */
99
+ export function SignInScreen({
100
+ mode = "sign-in",
101
+ title,
102
+ subtitle,
103
+ scheme,
104
+ redirectPath,
105
+ afterAuthHref = "/",
106
+ replaceOptions,
107
+ onSuccess,
108
+ showGoogle = true,
109
+ googleLabel,
110
+ signUpHref,
111
+ signInHref,
112
+ minPasswordLength,
113
+ errorDisplay = "toast",
114
+ testIDPrefix,
115
+ header,
116
+ footer,
117
+ onIncomplete,
118
+ stageTimeoutMs = DEFAULT_STAGE_TIMEOUT_MS
119
+ }) {
120
+ const signInState = useSignIn();
121
+ const signUpState = useSignUp();
122
+ const router = useRouter();
123
+ const colorScheme = useScheme();
124
+ const [email, setEmail] = useState("");
125
+ const [password, setPassword] = useState("");
126
+ const [code, setCode] = useState("");
127
+ const [pendingVerification, setPendingVerification] = useState(false);
128
+ const [pendingSecondFactor, setPendingSecondFactor] = useState(false);
129
+ const [pendingFirstFactor, setPendingFirstFactor] = useState(false);
130
+ const [firstFactors, setFirstFactors] = useState([]);
131
+ const [firstStrategy, setFirstStrategy] = useState("email_code");
132
+ const [firstLoading, setFirstLoading] = useState(false);
133
+ const [firstError, setFirstError] = useState("");
134
+ const [factors, setFactors] = useState([]);
135
+ const [clientTrust, setClientTrust] = useState(false);
136
+ const [pendingNewPassword, setPendingNewPassword] = useState(false);
137
+ const [newPassword, setNewPassword] = useState("");
138
+ const [confirmPassword, setConfirmPassword] = useState("");
139
+ const secondFactor = useSecondFactor({
140
+ stageTimeoutMs
141
+ });
142
+ const [error, setError] = useState("");
143
+ const [submitting, setSubmitting] = useState(false);
144
+ // The field each "next" key moves to.
145
+ const passwordField = useRef(null);
146
+ const confirmField = useRef(null);
147
+ const signUp = mode === "sign-up";
148
+ const id = name => `${testIDPrefix ?? mode}-${name}`;
149
+ const fail = (heading, message) => {
150
+ if (errorDisplay === "inline") setError(message);else toast.error(heading, message === heading ? undefined : {
151
+ description: message
152
+ });
153
+ };
154
+ const done = () => {
155
+ if (onSuccess) onSuccess();else router.replace(afterAuthHref, replaceOptions);
156
+ };
157
+ const warnIncomplete = attempt => {
158
+ console.warn(`[native-ui] Sign-in not complete: status=${attempt.status}, ` + `secondFactors=${JSON.stringify((attempt.supportedSecondFactors ?? []).map(f => f.strategy))}, ` + `firstFactors=${JSON.stringify((attempt.supportedFirstFactors ?? []).map(f_0 => f_0.strategy))}. ` + "Pass onIncomplete to handle it in the app.");
159
+ };
160
+
161
+ // A status the built-in UI cannot finish: the app's `onIncomplete`, or the web fallback.
162
+ const unhandled = attempt_0 => {
163
+ if (onIncomplete) return onIncomplete(attempt_0);
164
+ warnIncomplete(attempt_0);
165
+ fail(AUTH_MESSAGES.signInFailed, AUTH_MESSAGES.needsAnotherStep);
166
+ };
167
+ const chooseStrategy = async (strategy, list) => {
168
+ secondFactor.clearError();
169
+ secondFactor.setStrategy(strategy);
170
+ setPendingSecondFactor(true);
171
+ await secondFactor.prepare(strategy, list.find(factor => factor.strategy === strategy));
172
+ };
173
+ const startSecondFactor = async (attempt_1, trust) => {
174
+ const list_0 = attempt_1.supportedSecondFactors ?? [];
175
+ const strategy_0 = pickSecondFactorStrategy(list_0);
176
+ if (!strategy_0) return unhandled(attempt_1);
177
+ setFactors(list_0);
178
+ setClientTrust(trust);
179
+ await chooseStrategy(strategy_0, list_0);
180
+ };
181
+ const chooseFirstStrategy = async (strategy_1, list_1) => {
182
+ const {
183
+ signIn
184
+ } = signInState;
185
+ if (!signIn) return;
186
+ setFirstError("");
187
+ setFirstStrategy(strategy_1);
188
+ setPendingFirstFactor(true);
189
+ setFirstLoading(true);
190
+ const sent = await prepareFirstFactorIfNeeded({
191
+ signIn,
192
+ strategy: strategy_1,
193
+ factor: list_1.find(factor_0 => factor_0.strategy === strategy_1),
194
+ timeoutMs: stageTimeoutMs
195
+ });
196
+ if (!sent.ok) setFirstError(sent.error);
197
+ setFirstLoading(false);
198
+ };
199
+ const startFirstFactor = async attempt_2 => {
200
+ const list_2 = attempt_2.supportedFirstFactors ?? [];
201
+ const strategy_2 = pickFirstFactorStrategy(list_2);
202
+ if (!strategy_2) return unhandled(attempt_2);
203
+ setFirstFactors(list_2);
204
+ await chooseFirstStrategy(strategy_2, list_2);
205
+ };
206
+
207
+ // Routes a sign-in attempt. `needs_first_factor` is built in only after `signIn.create`.
208
+ const routeAttempt = async (attempt_3, afterCreate) => {
209
+ const {
210
+ setActive
211
+ } = signInState;
212
+ // Clerk's `SignInStatus` omits `needs_client_trust`: widen before comparing.
213
+ const status = attempt_3.status;
214
+ if (status === "complete" && attempt_3.createdSessionId && setActive) {
215
+ await withAuthTimeout(setActive({
216
+ session: attempt_3.createdSessionId
217
+ }), "set-active", stageTimeoutMs);
218
+ done();
219
+ } else if (status === "needs_new_password") {
220
+ setPendingFirstFactor(false);
221
+ setPendingNewPassword(true);
222
+ } else if (status === "needs_second_factor" || status === "needs_client_trust") {
223
+ setPendingFirstFactor(false);
224
+ await startSecondFactor(attempt_3, status === "needs_client_trust");
225
+ } else if (status === "needs_first_factor" && afterCreate) {
226
+ await startFirstFactor(attempt_3);
227
+ } else {
228
+ unhandled(attempt_3);
229
+ }
230
+ };
231
+ const onFirstFactor = async firstCode => {
232
+ const {
233
+ isLoaded,
234
+ signIn: signIn_0
235
+ } = signInState;
236
+ if (!isLoaded || !signIn_0 || firstLoading) return;
237
+ setFirstLoading(true);
238
+ setFirstError("");
239
+ const result = await submitFirstFactor({
240
+ signIn: signIn_0,
241
+ code: firstCode,
242
+ strategy: firstStrategy,
243
+ timeoutMs: stageTimeoutMs
244
+ });
245
+ if (!result.ok) {
246
+ setFirstError(result.error);
247
+ setFirstLoading(false);
248
+ return;
249
+ }
250
+ try {
251
+ await routeAttempt(result.attempt, false);
252
+ } catch (caught) {
253
+ if (isAuthTimeoutError(caught)) logAuthFailure("set-active", caught);
254
+ setFirstError(authErrorMessage(caught, AUTH_MESSAGES.verifyFailed));
255
+ } finally {
256
+ setFirstLoading(false);
257
+ }
258
+ };
259
+ const onNewPassword = async () => {
260
+ const {
261
+ isLoaded: isLoaded_0,
262
+ signIn: signIn_1,
263
+ setActive: setActive_0
264
+ } = signInState;
265
+ if (!isLoaded_0 || !signIn_1 || !setActive_0 || submitting) return;
266
+ if (!newPassword) return fail(AUTH_MESSAGES.resetPasswordFailed, AUTH_MESSAGES.missingFields);
267
+ if (minPasswordLength && newPassword.length < minPasswordLength) {
268
+ return fail(AUTH_MESSAGES.resetPasswordFailed, `Use at least ${minPasswordLength} characters for the password`);
269
+ }
270
+ if (newPassword !== confirmPassword) {
271
+ return fail(AUTH_MESSAGES.resetPasswordFailed, AUTH_MESSAGES.passwordMismatch);
272
+ }
273
+ setSubmitting(true);
274
+ setError("");
275
+ try {
276
+ const result_0 = await withAuthTimeout(signIn_1.resetPassword({
277
+ password: newPassword
278
+ }), "reset-password", stageTimeoutMs);
279
+ if (result_0.status === "complete" && result_0.createdSessionId) {
280
+ await withAuthTimeout(setActive_0({
281
+ session: result_0.createdSessionId
282
+ }), "set-active", stageTimeoutMs);
283
+ done();
284
+ } else if (onIncomplete) {
285
+ onIncomplete(result_0);
286
+ } else {
287
+ warnIncomplete(result_0);
288
+ fail(AUTH_MESSAGES.resetPasswordFailed, AUTH_MESSAGES.needsAnotherStep);
289
+ }
290
+ } catch (caught_0) {
291
+ if (isAuthTimeoutError(caught_0)) logAuthFailure("reset-password", caught_0);
292
+ fail(AUTH_MESSAGES.resetPasswordFailed, authErrorMessage(caught_0, AUTH_MESSAGES.resetPasswordFailed));
293
+ } finally {
294
+ setSubmitting(false);
295
+ }
296
+ };
297
+ const onSignIn = async () => {
298
+ const {
299
+ isLoaded: isLoaded_1,
300
+ signIn: signIn_2,
301
+ setActive: setActive_1
302
+ } = signInState;
303
+ if (!isLoaded_1 || !signIn_2 || !setActive_1 || submitting) return;
304
+ if (!email || !password) return fail(AUTH_MESSAGES.signInFailed, AUTH_MESSAGES.missingFields);
305
+ setSubmitting(true);
306
+ setError("");
307
+ try {
308
+ const attempt_4 = await withAuthTimeout(signIn_2.create({
309
+ identifier: email,
310
+ password
311
+ }), "create", stageTimeoutMs);
312
+ await routeAttempt(attempt_4, true);
313
+ } catch (caught_1) {
314
+ logAuthFailure("create", caught_1);
315
+ fail(AUTH_MESSAGES.signInFailed, authErrorMessage(caught_1, AUTH_MESSAGES.invalidCredentials, {
316
+ credentials: true
317
+ }));
318
+ } finally {
319
+ setSubmitting(false);
320
+ }
321
+ };
322
+ const onSecondFactor = async secondFactorCode => {
323
+ const result_1 = await secondFactor.submit(secondFactorCode);
324
+ if (result_1.ok) done();
325
+ };
326
+ const onSecondFactorBack = () => {
327
+ // The pending attempt stays; the next `signIn.create` replaces it.
328
+ setPendingSecondFactor(false);
329
+ setPendingFirstFactor(false);
330
+ setFirstError("");
331
+ setPendingNewPassword(false);
332
+ setNewPassword("");
333
+ setConfirmPassword("");
334
+ setPassword("");
335
+ setError("");
336
+ secondFactor.clearError();
337
+ };
338
+ const onSignUp = async () => {
339
+ const {
340
+ isLoaded: isLoaded_2,
341
+ signUp: attempt_5
342
+ } = signUpState;
343
+ if (!isLoaded_2 || !attempt_5 || submitting) return;
344
+ if (!email || !password) return fail(AUTH_MESSAGES.signUpFailed, AUTH_MESSAGES.missingFields);
345
+ if (minPasswordLength && password.length < minPasswordLength) {
346
+ return fail(AUTH_MESSAGES.signUpFailed, `Use at least ${minPasswordLength} characters for the password`);
347
+ }
348
+ setSubmitting(true);
349
+ setError("");
350
+ try {
351
+ await attempt_5.create({
352
+ emailAddress: email,
353
+ password
354
+ });
355
+ await attempt_5.prepareEmailAddressVerification({
356
+ strategy: "email_code"
357
+ });
358
+ setPendingVerification(true);
359
+ } catch (caught_2) {
360
+ fail(AUTH_MESSAGES.signUpFailed, authErrorMessage(caught_2, AUTH_MESSAGES.signUpFailed));
361
+ } finally {
362
+ setSubmitting(false);
363
+ }
364
+ };
365
+ const onVerify = async () => {
366
+ const {
367
+ isLoaded: isLoaded_3,
368
+ signUp: attempt_6,
369
+ setActive: setActive_2
370
+ } = signUpState;
371
+ if (!isLoaded_3 || !attempt_6 || !setActive_2 || submitting) return;
372
+ if (!code) return fail(AUTH_MESSAGES.verifyFailed, AUTH_MESSAGES.missingCode);
373
+ setSubmitting(true);
374
+ setError("");
375
+ try {
376
+ const result_2 = await attempt_6.attemptEmailAddressVerification({
377
+ code
378
+ });
379
+ if (result_2.status === "complete" && result_2.createdSessionId) {
380
+ await setActive_2({
381
+ session: result_2.createdSessionId
382
+ });
383
+ done();
384
+ } else {
385
+ fail(AUTH_MESSAGES.verifyFailed, AUTH_MESSAGES.needsAnotherStep);
386
+ }
387
+ } catch (caught_3) {
388
+ fail(AUTH_MESSAGES.verifyFailed, authErrorMessage(caught_3, AUTH_MESSAGES.verifyFailed));
389
+ } finally {
390
+ setSubmitting(false);
391
+ }
392
+ };
393
+ const linkHref = signUp ? signInHref : signUpHref;
394
+ const footerRow = <>
395
+ {linkHref && !pendingVerification ? <>
396
+ <Text className="font-instrument-sans text-sm" style={textStyle("muted", colorScheme)}>
397
+ {signUp ? "Already have an account?" : "Don't have an account?"}
398
+ </Text>
399
+ <Button testID={id(signUp ? "sign-in-link" : "sign-up-link")} variant="link" size="sm" fullWidth={false} onPress={() => router.push(linkHref)}>
400
+ {signUp ? "Sign in" : "Sign up"}
401
+ </Button>
402
+ </> : null}
403
+ {footer}
404
+ </>;
405
+ const hasFooter = Boolean(linkHref && !pendingVerification || footer);
406
+ if (pendingNewPassword) {
407
+ return <AuthScreen testID={id("new-password-screen")} title={AUTH_MESSAGES.newPasswordTitle} subtitle={AUTH_MESSAGES.newPasswordDescription} header={header}>
408
+ <PasswordInput testID={id("new-password")} accessibilityLabel={AUTH_MESSAGES.newPasswordLabel} autoCapitalize="none" autoComplete="new-password" textContentType="newPassword" autoFocus returnKeyType="next" submitBehavior="submit" onSubmitEditing={() => confirmField.current?.focus()} value={newPassword} placeholder={AUTH_MESSAGES.newPasswordLabel} onChangeText={setNewPassword} />
409
+ <PasswordInput testID={id("confirm-password")} ref={confirmField} accessibilityLabel={AUTH_MESSAGES.confirmPasswordLabel} autoCapitalize="none" autoComplete="new-password" textContentType="newPassword" returnKeyType="go" value={confirmPassword} placeholder={AUTH_MESSAGES.confirmPasswordLabel} onChangeText={setConfirmPassword} onSubmitEditing={() => void onNewPassword()} />
410
+ <FieldError testID={id("error")} message={error} />
411
+ <Button testID={id("new-password-submit")} loading={submitting} onPress={() => void onNewPassword()}>
412
+ {AUTH_MESSAGES.setPassword}
413
+ </Button>
414
+ <Button testID={id("new-password-back")} variant="ghost" onPress={onSecondFactorBack}>
415
+ Use a different account
416
+ </Button>
417
+ </AuthScreen>;
418
+ }
419
+ if (pendingFirstFactor && !pendingSecondFactor && !pendingNewPassword) {
420
+ const others = supportedFirstFactorStrategies(firstFactors).filter(strategy_3 => strategy_3 !== firstStrategy);
421
+ const switchTo = others[0] ?? null;
422
+ const copy = secondFactorCopy(firstStrategy, false);
423
+ return <SecondFactorScreen key={`first-${firstStrategy}`} title={copy.title} description={copy.description} footer={switchTo ? <Button testID={id("first-factor-switch")} variant="link" size="sm" fullWidth={false} disabled={firstLoading} onPress={() => void chooseFirstStrategy(switchTo, firstFactors)}>
424
+ {METHOD_LABEL[switchTo]}
425
+ </Button> : undefined} onSubmit={onFirstFactor} error={firstError} loading={firstLoading} onBack={onSecondFactorBack} backLabel="Use a different account" header={header} testIDPrefix={id("first-factor")} />;
426
+ }
427
+ if (pendingSecondFactor) {
428
+ const active = secondFactor.strategy;
429
+ const available = supportedSecondFactorStrategies(factors);
430
+ const others_0 = available.filter(strategy_4 => strategy_4 !== active);
431
+ // From a backup code, go back to the default non-backup method. Otherwise offer a backup code.
432
+ const switchTo_0 = active === "backup_code" ? pickSecondFactorStrategy(factors.filter(factor_1 => factor_1.strategy !== "backup_code")) : others_0.includes("backup_code") ? "backup_code" : null;
433
+ const copy_0 = secondFactorCopy(active, clientTrust);
434
+ const backup = active === "backup_code";
435
+ return <SecondFactorScreen key={active} title={copy_0.title} description={copy_0.description} codeLength={backup ? 8 : 6} digitsOnly={!backup} placeholder="Backup code" footer={switchTo_0 ? <Button testID={id("second-factor-switch")} variant="link" size="sm" fullWidth={false} disabled={secondFactor.loading} onPress={() => void chooseStrategy(switchTo_0, factors)}>
436
+ {METHOD_LABEL[switchTo_0]}
437
+ </Button> : undefined} onSubmit={onSecondFactor} error={secondFactor.error} loading={secondFactor.loading} onBack={onSecondFactorBack} backLabel="Use a different account" header={header} testIDPrefix={id("second-factor")} />;
438
+ }
439
+ if (pendingVerification) {
440
+ return <AuthScreen testID={id("screen")} title="Check your email" subtitle="Enter the code we sent to your email." header={header} footer={hasFooter ? footerRow : undefined}>
441
+ <Input testID={id("code")} accessibilityLabel="Verification code" value={code} placeholder="Verification code" keyboardType="number-pad" autoComplete="one-time-code" textContentType="oneTimeCode" autoFocus returnKeyType="go" onChangeText={setCode} onSubmitEditing={() => void onVerify()} />
442
+ <FieldError testID={id("error")} message={error} />
443
+ <Button testID={id("verify")} loading={submitting} onPress={() => void onVerify()}>
444
+ Verify
445
+ </Button>
446
+ <Button testID={id("back")} variant="ghost" onPress={() => {
447
+ setPendingVerification(false);
448
+ setCode("");
449
+ setError("");
450
+ }}>
451
+ Use a different email
452
+ </Button>
453
+ </AuthScreen>;
454
+ }
455
+ return <AuthScreen testID={id("screen")} title={title ?? (signUp ? "Create account" : "Welcome back")} subtitle={subtitle ?? (signUp ? "Sign up to get started" : "Sign in to continue")} header={header} footer={hasFooter ? footerRow : undefined}>
456
+ {showGoogle ? <>
457
+ <GoogleSignInButton testID={id("google")} scheme={scheme} redirectPath={redirectPath} afterAuthHref={afterAuthHref} replaceOptions={replaceOptions} onSuccess={onSuccess} onError={message_0 => fail(message_0, message_0)} label={googleLabel} disabled={submitting} />
458
+ <OrDivider />
459
+ </> : null}
460
+ <Input testID={id("email")} accessibilityLabel="Email" autoCapitalize="none" autoComplete="email" textContentType="emailAddress" keyboardType="email-address" returnKeyType="next" submitBehavior="submit" onSubmitEditing={() => passwordField.current?.focus()} value={email} placeholder="Email" onChangeText={setEmail} />
461
+ <PasswordInput testID={id("password")} ref={passwordField} accessibilityLabel="Password" autoCapitalize="none" autoComplete={signUp ? "new-password" : "password"} textContentType={signUp ? "newPassword" : "password"} returnKeyType="go" value={password} placeholder="Password" onChangeText={setPassword} onSubmitEditing={() => void (signUp ? onSignUp() : onSignIn())} />
462
+ <FieldError testID={id("error")} message={error} />
463
+ <Button testID={id("submit")} loading={submitting} onPress={() => void (signUp ? onSignUp() : onSignIn())}>
464
+ {signUp ? "Create account" : "Sign in"}
465
+ </Button>
466
+ {/* Bot protection mounts its widget here on web sign-up. */}
467
+ {signUp ? <View nativeID="clerk-captcha" /> : null}
468
+ </AuthScreen>;
469
+ }
470
+ /** `SignInScreen` with `mode="sign-up"`. */
471
+ export function SignUpScreen(props) {
472
+ const $ = _c(2);
473
+ let t0;
474
+ if ($[0] !== props) {
475
+ t0 = <SignInScreen {...props} mode="sign-up" />;
476
+ $[0] = props;
477
+ $[1] = t0;
478
+ } else {
479
+ t0 = $[1];
480
+ }
481
+ return t0;
482
+ }
@@ -0,0 +1,131 @@
1
+ import { isClerkAPIResponseError } from "@clerk/clerk-expo";
2
+
3
+ /** Words that must not reach the user: the auth provider and stack names. */
4
+ const PRODUCT_NAMES = /\b(clerk|convex)\b/gi;
5
+
6
+ /** The only Clerk codes that mean "Invalid email or password". */
7
+ export const CREDENTIAL_ERROR_CODES = ["form_password_incorrect", "form_identifier_not_found"];
8
+ const asString = value => typeof value === "string" && value.length > 0 ? value : undefined;
9
+
10
+ /** Reads a Clerk API error, or a plain `{ errors: [...] }` object. No `zod`. */
11
+ function parseAuthError(error) {
12
+ if (typeof error !== "object" || error === null) return null;
13
+ const record = error;
14
+ if (!isClerkAPIResponseError(error) && !Array.isArray(record.errors)) return null;
15
+ const errors = Array.isArray(record.errors) ? record.errors.filter(item => typeof item === "object" && item !== null) : [];
16
+ return {
17
+ errors,
18
+ status: typeof record.status === "number" ? record.status : undefined,
19
+ clerkTraceId: asString(record.clerkTraceId),
20
+ errorName: asString(record.name)
21
+ };
22
+ }
23
+ function firstError(parsed) {
24
+ const first = parsed?.errors[0];
25
+ return {
26
+ code: asString(first?.code),
27
+ message: asString(first?.message),
28
+ longMessage: asString(first?.longMessage),
29
+ paramName: asString(first?.meta?.paramName)
30
+ };
31
+ }
32
+
33
+ /** The code of a timed-out auth stage. */
34
+ export const AUTH_TIMEOUT_CODE = "auth_timeout";
35
+
36
+ /** Default time for one Clerk call, in milliseconds. */
37
+ export const DEFAULT_STAGE_TIMEOUT_MS = 20000;
38
+
39
+ /**
40
+ * The error `withAuthTimeout` rejects with. It is Clerk-shaped (`errors[0].code`
41
+ * is `auth_timeout`), so `authErrorMessage` and `logAuthFailure` read it.
42
+ */
43
+ export class AuthTimeoutError extends Error {
44
+ code = AUTH_TIMEOUT_CODE;
45
+ constructor(stage, ms) {
46
+ const message = `The ${stage} step timed out after ${Math.round(ms / 1000)} seconds. Try again.`;
47
+ super(message);
48
+ this.name = "AuthTimeoutError";
49
+ this.stage = stage;
50
+ this.errors = [{
51
+ code: AUTH_TIMEOUT_CODE,
52
+ message
53
+ }];
54
+ }
55
+ }
56
+ export function isAuthTimeoutError(error) {
57
+ return error instanceof AuthTimeoutError;
58
+ }
59
+
60
+ /**
61
+ * Rejects with `AuthTimeoutError` (code `auth_timeout`, message names `stage`)
62
+ * when `promise` has not settled after `ms`. The timer is cleared on settle.
63
+ */
64
+ export function withAuthTimeout(promise, stage, ms = DEFAULT_STAGE_TIMEOUT_MS) {
65
+ return new Promise((resolve, reject) => {
66
+ const timer = setTimeout(() => reject(new AuthTimeoutError(stage, ms)), ms);
67
+ promise.then(value => {
68
+ clearTimeout(timer);
69
+ resolve(value);
70
+ }, error => {
71
+ clearTimeout(timer);
72
+ reject(error);
73
+ });
74
+ });
75
+ }
76
+
77
+ /**
78
+ * Logs a failed auth call as one structured `console.error`. It holds the
79
+ * Clerk code, message, longMessage and meta.paramName, plus stage, errorName,
80
+ * status and clerkTraceId when present. For `AuthTimeoutError` the logged stage is the error's own stage. Only the error object is read, so a
81
+ * password or one-time code is never logged.
82
+ */
83
+ export function logAuthFailure(stage, error) {
84
+ if (isAuthTimeoutError(error)) stage = error.stage;
85
+ const parsed = parseAuthError(error);
86
+ const {
87
+ code,
88
+ message,
89
+ longMessage,
90
+ paramName
91
+ } = firstError(parsed);
92
+ const errorName = parsed?.errorName ?? (error instanceof Error ? error.name : undefined);
93
+ console.error("[sign-in] failure", {
94
+ stage,
95
+ ...(errorName ? {
96
+ errorName
97
+ } : {}),
98
+ ...(parsed?.status !== undefined ? {
99
+ status: parsed.status
100
+ } : {}),
101
+ ...(parsed?.clerkTraceId ? {
102
+ clerkTraceId: parsed.clerkTraceId
103
+ } : {}),
104
+ code,
105
+ message,
106
+ longMessage,
107
+ meta: {
108
+ paramName
109
+ }
110
+ });
111
+ }
112
+ /**
113
+ * A message to show for an auth error. For a Clerk error it gives the first
114
+ * `longMessage` or `message`, with `clerk` and `convex` written as "the
115
+ * sign-in service", then `(code: <code>)`. With no text it gives `fallback`,
116
+ * still followed by the code when there is one. With `credentials: true`, the
117
+ * two credential codes give `fallback` alone. Anything that is not a Clerk
118
+ * error gives `fallback`. Never shows a raw exception message.
119
+ */
120
+ export function authErrorMessage(error, fallback, options = {}) {
121
+ const parsed = parseAuthError(error);
122
+ if (!parsed) return fallback;
123
+ const {
124
+ code,
125
+ message,
126
+ longMessage
127
+ } = firstError(parsed);
128
+ if (options.credentials && code && CREDENTIAL_ERROR_CODES.includes(code)) return fallback;
129
+ const text = (longMessage ?? message)?.replace(PRODUCT_NAMES, "the sign-in service") ?? fallback;
130
+ return code ? `${text} (code: ${code})` : text;
131
+ }
@@ -0,0 +1,11 @@
1
+ // Clerk + Convex providers and the shared sign-in and sign-up screens. On
2
+ // `@vvedantb/native-ui/auth` only: they import `@clerk/clerk-expo`, `convex`,
3
+ // `expo-auth-session`, `expo-web-browser` and `expo-router`, and Metro does not
4
+ // tree-shake.
5
+ export { AuthProviders } from "./AuthProviders";
6
+ export { AuthScreen, OrDivider, FieldError } from "./AuthScreen";
7
+ export { authErrorMessage, logAuthFailure, withAuthTimeout, AuthTimeoutError, isAuthTimeoutError, AUTH_TIMEOUT_CODE, DEFAULT_STAGE_TIMEOUT_MS, CREDENTIAL_ERROR_CODES } from "./authErrors";
8
+ export { GoogleSignInButton, useGoogleSignIn, useWarmUpBrowser, GOOGLE_ERROR_MESSAGE } from "./GoogleSignInButton";
9
+ export { SignInScreen, SignUpScreen, AUTH_MESSAGES } from "./SignInScreen";
10
+ export { SecondFactorScreen } from "./SecondFactorScreen";
11
+ export { submitTotpSecondFactor, submitSecondFactor, prepareSecondFactorIfNeeded, prepareFirstFactorIfNeeded, submitFirstFactor, pickFirstFactorStrategy, supportedFirstFactorStrategies, FIRST_FACTOR_PREFERENCE, pickSecondFactorStrategy, supportedSecondFactorStrategies, SECOND_FACTOR_PREFERENCE, useSecondFactor, TOTP_MESSAGES } from "./totp";