@atzentis/auth-expo 0.0.16 → 0.2.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.js CHANGED
@@ -1,6 +1,2664 @@
1
+ import { StyleSheet, View, Text, TextInput, Pressable, KeyboardAvoidingView, ScrollView, Platform, ActivityIndicator, Alert, FlatList, RefreshControl, ActionSheetIOS, Image } from 'react-native';
2
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
+ import { InvalidConfigError, authKeys, RequestFailedError, forgotPasswordSchema, credentialLoginSchema, magicLinkSchema, NotImplementedError, phoneLoginSchema, otpVerifySchema, resetPasswordSchema, signupSchema, totpSchema, backupCodeSchema, changeEmailSchema, changePasswordSchema, updateNameSchema, updateUsernameSchema } from '@atzentis/auth-sdk';
4
+ import { QueryClient, QueryClientProvider, useQuery, useQueryClient, useMutation } from '@tanstack/react-query';
5
+ import React, { forwardRef, useState, useMemo, use, useCallback, useEffect } from 'react';
6
+ import { en } from '@atzentis/auth-locales';
7
+ import { expoClient } from '@better-auth/expo/client';
8
+ import { createAuthClient } from 'better-auth/react';
9
+ import { getLocales } from 'expo-localization';
10
+ import * as SecureStore from 'expo-secure-store';
11
+ import { EyeOff, Eye, AlertCircle, CheckCircle2 } from 'lucide-react-native';
12
+ import { Host, Button } from '@expo/ui/swift-ui';
13
+ import { useRouter } from 'expo-router';
14
+ import * as Haptics from 'expo-haptics';
15
+ import { zodResolver } from '@hookform/resolvers/zod';
16
+ import { useForm, Controller } from 'react-hook-form';
17
+ import * as ImagePicker from 'expo-image-picker';
18
+ import * as LocalAuthentication from 'expo-local-authentication';
19
+
20
+ // src/ui/layouts/auth-container.tsx
21
+ function AuthContainer({ children }) {
22
+ return /* @__PURE__ */ jsx(
23
+ KeyboardAvoidingView,
24
+ {
25
+ behavior: Platform.OS === "ios" ? "padding" : "height",
26
+ style: styles.flex,
27
+ children: /* @__PURE__ */ jsx(
28
+ ScrollView,
29
+ {
30
+ contentInsetAdjustmentBehavior: "automatic",
31
+ keyboardShouldPersistTaps: "handled",
32
+ contentContainerStyle: styles.scrollContent,
33
+ children: /* @__PURE__ */ jsx(View, { style: styles.container, children })
34
+ }
35
+ )
36
+ }
37
+ );
38
+ }
39
+ var styles = StyleSheet.create({
40
+ flex: { flex: 1 },
41
+ scrollContent: { flexGrow: 1, justifyContent: "center" },
42
+ container: {
43
+ flex: 1,
44
+ justifyContent: "center",
45
+ paddingHorizontal: 24,
46
+ gap: 16,
47
+ maxWidth: 440,
48
+ alignSelf: "center",
49
+ width: "100%"
50
+ }
51
+ });
52
+ var AuthClientContext = React.createContext(null);
53
+ var LocalizationContext = React.createContext({
54
+ localization: en,
55
+ localizeErrors: true
56
+ });
57
+ function AuthProvider({
58
+ baseURL,
59
+ children,
60
+ locales,
61
+ localization: localizationOverride,
62
+ queryClient: externalQueryClient,
63
+ scheme,
64
+ storagePrefix
65
+ }) {
66
+ const queryClient = useMemo(
67
+ () => externalQueryClient ?? new QueryClient(),
68
+ [externalQueryClient]
69
+ );
70
+ const authClient = useMemo(
71
+ () => createAuthClient({
72
+ baseURL,
73
+ plugins: [
74
+ expoClient({
75
+ scheme,
76
+ storagePrefix: storagePrefix ?? scheme,
77
+ storage: SecureStore
78
+ })
79
+ ]
80
+ }),
81
+ [baseURL, scheme, storagePrefix]
82
+ );
83
+ const resolvedLocalization = useMemo(() => {
84
+ const deviceLocales = getLocales();
85
+ const langCode = deviceLocales[0]?.languageCode ?? "en";
86
+ const base = locales?.[langCode] ?? en;
87
+ if (localizationOverride) {
88
+ return { ...base, ...localizationOverride };
89
+ }
90
+ return base;
91
+ }, [locales, localizationOverride]);
92
+ return /* @__PURE__ */ jsx(QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsx(AuthClientContext, { value: authClient, children: /* @__PURE__ */ jsx(LocalizationContext, { value: { localization: resolvedLocalization, localizeErrors: true }, children }) }) });
93
+ }
94
+
95
+ // src/hooks/use-auth-client.ts
96
+ function useAuthClient() {
97
+ const client = use(AuthClientContext);
98
+ if (!client) {
99
+ throw new InvalidConfigError({
100
+ message: "useAuthClient must be used within an AuthProvider"
101
+ });
102
+ }
103
+ return client;
104
+ }
105
+
106
+ // src/hooks/queries/use-session.ts
107
+ function useSession() {
108
+ const client = useAuthClient();
109
+ return useQuery({
110
+ queryKey: authKeys.session(),
111
+ queryFn: async () => {
112
+ const response = await client.getSession();
113
+ if (response.error) return null;
114
+ return response.data?.session ?? null;
115
+ },
116
+ staleTime: 5 * 60 * 1e3
117
+ });
118
+ }
119
+ function AuthGate({ children, fallback = null }) {
120
+ const { data: session } = useSession();
121
+ return /* @__PURE__ */ jsx(Fragment, { children: session ? children : fallback });
122
+ }
123
+ function ErrorFeedback({ message }) {
124
+ return /* @__PURE__ */ jsxs(View, { style: styles2.container, accessibilityRole: "alert", accessibilityLiveRegion: "polite", children: [
125
+ /* @__PURE__ */ jsx(AlertCircle, { size: 16 }),
126
+ /* @__PURE__ */ jsx(Text, { style: styles2.text, children: message })
127
+ ] });
128
+ }
129
+ var styles2 = StyleSheet.create({
130
+ container: {
131
+ flexDirection: "row",
132
+ alignItems: "center",
133
+ gap: 8,
134
+ padding: 12,
135
+ borderRadius: 8,
136
+ backgroundColor: "rgba(239, 68, 68, 0.1)"
137
+ },
138
+ text: { fontSize: 14, color: "#ef4444", flex: 1 }
139
+ });
140
+ var FormInput = forwardRef(
141
+ ({ error, label, style, ...props }, ref) => {
142
+ return /* @__PURE__ */ jsxs(View, { style: { gap: 4 }, children: [
143
+ label && /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, fontWeight: "500", color: "#1f2937" }, children: label }),
144
+ /* @__PURE__ */ jsx(
145
+ TextInput,
146
+ {
147
+ ref,
148
+ style: [
149
+ {
150
+ borderWidth: 1,
151
+ borderColor: error ? "#ef4444" : "#d1d5db",
152
+ borderRadius: 8,
153
+ paddingHorizontal: 12,
154
+ paddingVertical: 12,
155
+ fontSize: 16,
156
+ color: "#1f2937",
157
+ backgroundColor: "#ffffff",
158
+ minHeight: 44
159
+ },
160
+ style
161
+ ],
162
+ placeholderTextColor: "#9ca3af",
163
+ ...props
164
+ }
165
+ ),
166
+ error && /* @__PURE__ */ jsx(Text, { style: { fontSize: 12, color: "#ef4444" }, children: error })
167
+ ] });
168
+ }
169
+ );
170
+ FormInput.displayName = "FormInput";
171
+ var FormPasswordInput = forwardRef(
172
+ ({ error, label, style, ...props }, ref) => {
173
+ const [visible, setVisible] = useState(false);
174
+ return /* @__PURE__ */ jsxs(View, { style: { gap: 4 }, children: [
175
+ label && /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, fontWeight: "500", color: "#1f2937" }, children: label }),
176
+ /* @__PURE__ */ jsxs(View, { style: { position: "relative" }, children: [
177
+ /* @__PURE__ */ jsx(
178
+ TextInput,
179
+ {
180
+ ref,
181
+ secureTextEntry: !visible,
182
+ style: [
183
+ {
184
+ borderWidth: 1,
185
+ borderColor: error ? "#ef4444" : "#d1d5db",
186
+ borderRadius: 8,
187
+ paddingHorizontal: 12,
188
+ paddingVertical: 12,
189
+ paddingRight: 44,
190
+ fontSize: 16,
191
+ color: "#1f2937",
192
+ backgroundColor: "#ffffff",
193
+ minHeight: 44
194
+ },
195
+ style
196
+ ],
197
+ placeholderTextColor: "#9ca3af",
198
+ ...props
199
+ }
200
+ ),
201
+ /* @__PURE__ */ jsx(
202
+ Pressable,
203
+ {
204
+ onPress: () => setVisible((v) => !v),
205
+ accessibilityRole: "button",
206
+ accessibilityLabel: visible ? "Hide password" : "Show password",
207
+ style: {
208
+ position: "absolute",
209
+ right: 8,
210
+ top: 0,
211
+ bottom: 0,
212
+ justifyContent: "center",
213
+ alignItems: "center",
214
+ width: 32
215
+ },
216
+ children: visible ? /* @__PURE__ */ jsx(EyeOff, { size: 20 }) : /* @__PURE__ */ jsx(Eye, { size: 20 })
217
+ }
218
+ )
219
+ ] }),
220
+ error && /* @__PURE__ */ jsx(Text, { style: { fontSize: 12, color: "#ef4444" }, children: error })
221
+ ] });
222
+ }
223
+ );
224
+ FormPasswordInput.displayName = "FormPasswordInput";
225
+ function useAuthLocalization() {
226
+ return use(LocalizationContext);
227
+ }
228
+ function OAuthButton({ onError, onSuccess, provider }) {
229
+ const { localization: L } = useAuthLocalization();
230
+ const router = useRouter();
231
+ const client = useAuthClient();
232
+ const queryClient = useQueryClient();
233
+ const oauthMutation = useMutation({
234
+ mutationFn: async () => {
235
+ const response = await client.signIn.social({ provider });
236
+ if (response.error) {
237
+ throw new RequestFailedError({ message: response.error.message ?? "OAuth sign-in failed" });
238
+ }
239
+ return response.data;
240
+ },
241
+ onSuccess: (data) => {
242
+ if ("url" in data && data.url) {
243
+ queryClient.invalidateQueries({ queryKey: authKeys.session() });
244
+ }
245
+ if ("user" in data && data.user) {
246
+ queryClient.setQueryData(authKeys.user(), data.user);
247
+ }
248
+ }
249
+ });
250
+ const handlePress = useCallback(() => {
251
+ oauthMutation.mutate(void 0, {
252
+ onSuccess: () => {
253
+ if (onSuccess) {
254
+ onSuccess();
255
+ } else {
256
+ router.replace("/(app)");
257
+ }
258
+ },
259
+ onError: (error) => {
260
+ if (onError) {
261
+ onError(error);
262
+ }
263
+ }
264
+ });
265
+ }, [oauthMutation, router, onSuccess, onError]);
266
+ const label = `${L.CONTINUE_WITH} ${provider.charAt(0).toUpperCase()}${provider.slice(1)}`;
267
+ const loadingLabel = L.SIGNING_IN;
268
+ return /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: handlePress, label: oauthMutation.isPending ? loadingLabel : label }) });
269
+ }
270
+ function ProtectedScreen({ children, fallback = null }) {
271
+ const { data: session, isLoading } = useSession();
272
+ const router = useRouter();
273
+ useEffect(() => {
274
+ if (!isLoading && !session) {
275
+ router.replace("/sign-in");
276
+ }
277
+ }, [session, isLoading, router]);
278
+ if (isLoading) return /* @__PURE__ */ jsx(Fragment, { children: fallback });
279
+ if (!session) return null;
280
+ return /* @__PURE__ */ jsx(Fragment, { children });
281
+ }
282
+ function SuccessFeedback({ message }) {
283
+ return /* @__PURE__ */ jsxs(View, { style: styles3.container, accessibilityRole: "alert", accessibilityLiveRegion: "polite", children: [
284
+ /* @__PURE__ */ jsx(CheckCircle2, { size: 16 }),
285
+ /* @__PURE__ */ jsx(Text, { style: styles3.text, children: message })
286
+ ] });
287
+ }
288
+ var styles3 = StyleSheet.create({
289
+ container: {
290
+ flexDirection: "row",
291
+ alignItems: "center",
292
+ gap: 8,
293
+ padding: 12,
294
+ borderRadius: 8,
295
+ backgroundColor: "rgba(34, 197, 94, 0.1)"
296
+ },
297
+ text: { fontSize: 14, color: "#22c55e", flex: 1 }
298
+ });
299
+ function SubmitButton({
300
+ label,
301
+ loading,
302
+ loadingLabel,
303
+ onPress,
304
+ disabled = false
305
+ }) {
306
+ const isDisabled = disabled || loading;
307
+ const handlePress = () => {
308
+ if (isDisabled) return;
309
+ if (process.env.EXPO_OS === "ios") {
310
+ Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
311
+ }
312
+ onPress();
313
+ };
314
+ return /* @__PURE__ */ jsx(Host, { matchContents: true, style: { opacity: isDisabled ? 0.5 : 1 }, children: /* @__PURE__ */ jsx(Button, { onPress: handlePress, label: loading ? loadingLabel : label }) });
315
+ }
316
+ function EmailVerificationScreen({ onSuccess, token }) {
317
+ const { localization: L } = useAuthLocalization();
318
+ const router = useRouter();
319
+ const client = useAuthClient();
320
+ const [status, setStatus] = useState(token ? "loading" : "no_token");
321
+ const verifyMutation = useMutation({
322
+ mutationFn: async (verifyToken) => {
323
+ const response = await client.verifyEmail?.({ query: { token: verifyToken } });
324
+ if (response?.error) {
325
+ throw new RequestFailedError({ message: response.error.message ?? L.REQUEST_FAILED });
326
+ }
327
+ return response?.data;
328
+ }
329
+ });
330
+ useEffect(() => {
331
+ if (!token) return;
332
+ setStatus("loading");
333
+ verifyMutation.mutate(token, {
334
+ onSuccess: () => {
335
+ setStatus("success");
336
+ setTimeout(() => {
337
+ if (onSuccess) {
338
+ onSuccess();
339
+ } else {
340
+ router.replace("/(app)");
341
+ }
342
+ }, 2e3);
343
+ },
344
+ onError: () => {
345
+ setStatus("error");
346
+ }
347
+ });
348
+ }, [token]);
349
+ const handleRetry = useCallback(() => {
350
+ if (!token) return;
351
+ setStatus("loading");
352
+ verifyMutation.mutate(token, {
353
+ onSuccess: () => {
354
+ setStatus("success");
355
+ setTimeout(() => {
356
+ if (onSuccess) {
357
+ onSuccess();
358
+ } else {
359
+ router.replace("/(app)");
360
+ }
361
+ }, 2e3);
362
+ },
363
+ onError: () => {
364
+ setStatus("error");
365
+ }
366
+ });
367
+ }, [token, verifyMutation, router, onSuccess]);
368
+ return /* @__PURE__ */ jsx(AuthContainer, { children: /* @__PURE__ */ jsxs(View, { style: { gap: 24 }, children: [
369
+ /* @__PURE__ */ jsx(View, { style: { gap: 4, alignItems: "center" }, children: /* @__PURE__ */ jsx(Text, { style: { fontSize: 24, fontWeight: "700", color: "#1f2937" }, children: L.EMAIL_VERIFICATION }) }),
370
+ status === "loading" && /* @__PURE__ */ jsxs(View, { style: { alignItems: "center", gap: 16 }, children: [
371
+ /* @__PURE__ */ jsx(ActivityIndicator, { size: "large" }),
372
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 16, color: "#6b7280" }, children: L.EMAIL_VERIFICATION_CHECKING })
373
+ ] }),
374
+ status === "success" && /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
375
+ /* @__PURE__ */ jsx(SuccessFeedback, { message: L.EMAIL_VERIFICATION_SUCCESS }),
376
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#6b7280", textAlign: "center" }, children: L.REDIRECTING })
377
+ ] }),
378
+ status === "error" && /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
379
+ /* @__PURE__ */ jsx(ErrorFeedback, { message: verifyMutation.error?.message ?? L.REQUEST_FAILED }),
380
+ /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: handleRetry, label: L.VERIFY }) }),
381
+ /* @__PURE__ */ jsx(View, { style: { alignItems: "center" }, children: /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => router.back(), label: L.GO_BACK }) }) })
382
+ ] }),
383
+ status === "no_token" && /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
384
+ /* @__PURE__ */ jsx(ErrorFeedback, { message: L.REQUEST_FAILED }),
385
+ /* @__PURE__ */ jsx(View, { style: { alignItems: "center" }, children: /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => router.back(), label: L.GO_BACK }) }) })
386
+ ] })
387
+ ] }) });
388
+ }
389
+ function useRequestPasswordReset() {
390
+ const client = useAuthClient();
391
+ return useMutation({
392
+ mutationFn: async (data) => {
393
+ try {
394
+ await client.requestPasswordReset({
395
+ email: data.email,
396
+ redirectTo: data.redirectTo
397
+ });
398
+ return true;
399
+ } catch (error) {
400
+ const message = error instanceof Error && error.message ? error.message : "Password reset request failed";
401
+ throw new RequestFailedError({ message });
402
+ }
403
+ }
404
+ });
405
+ }
406
+ function ForgotPasswordScreen({ onSuccess }) {
407
+ const { localization: L } = useAuthLocalization();
408
+ const router = useRouter();
409
+ const forgotMutation = useRequestPasswordReset();
410
+ const [emailSent, setEmailSent] = useState(false);
411
+ const [cooldown, setCooldown] = useState(0);
412
+ const form = useForm({
413
+ resolver: zodResolver(forgotPasswordSchema),
414
+ defaultValues: { email: "" },
415
+ mode: "onChange"
416
+ });
417
+ const handleSubmit = useCallback(
418
+ (data) => {
419
+ forgotMutation.mutate(
420
+ { email: data.email },
421
+ {
422
+ onSuccess: () => {
423
+ setEmailSent(true);
424
+ setCooldown(60);
425
+ if (onSuccess) {
426
+ onSuccess();
427
+ }
428
+ }
429
+ }
430
+ );
431
+ },
432
+ [forgotMutation, onSuccess]
433
+ );
434
+ const handleResend = useCallback(() => {
435
+ if (cooldown > 0) return;
436
+ const email = form.getValues("email");
437
+ forgotMutation.mutate({ email }, { onSuccess: () => setCooldown(60) });
438
+ }, [forgotMutation, cooldown, form]);
439
+ useEffect(() => {
440
+ if (cooldown <= 0) return;
441
+ const timer = setTimeout(() => setCooldown((c) => c - 1), 1e3);
442
+ return () => clearTimeout(timer);
443
+ }, [cooldown]);
444
+ return /* @__PURE__ */ jsx(AuthContainer, { children: /* @__PURE__ */ jsxs(View, { style: { gap: 24 }, children: [
445
+ /* @__PURE__ */ jsxs(View, { style: { gap: 4, alignItems: "center" }, children: [
446
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 24, fontWeight: "700", color: "#1f2937" }, children: L.FORGOT_PASSWORD }),
447
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#6b7280" }, children: L.FORGOT_PASSWORD_DESCRIPTION })
448
+ ] }),
449
+ !emailSent ? /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
450
+ /* @__PURE__ */ jsx(
451
+ Controller,
452
+ {
453
+ control: form.control,
454
+ name: "email",
455
+ render: ({ field: { onChange, onBlur, value }, fieldState: { error } }) => /* @__PURE__ */ jsx(
456
+ FormInput,
457
+ {
458
+ label: L.EMAIL_PLACEHOLDER,
459
+ placeholder: L.EMAIL_PLACEHOLDER,
460
+ autoCapitalize: "none",
461
+ keyboardType: "email-address",
462
+ editable: !forgotMutation.isPending,
463
+ onChangeText: onChange,
464
+ onBlur,
465
+ value,
466
+ error: error?.message
467
+ }
468
+ )
469
+ }
470
+ ),
471
+ forgotMutation.error && /* @__PURE__ */ jsx(ErrorFeedback, { message: forgotMutation.error.message }),
472
+ /* @__PURE__ */ jsx(
473
+ SubmitButton,
474
+ {
475
+ label: L.FORGOT_PASSWORD_ACTION,
476
+ loading: forgotMutation.isPending,
477
+ loadingLabel: L.SENDING,
478
+ onPress: form.handleSubmit(handleSubmit)
479
+ }
480
+ ),
481
+ /* @__PURE__ */ jsx(View, { style: { alignItems: "center", marginTop: 8 }, children: /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => router.back(), label: L.GO_BACK }) }) })
482
+ ] }) : /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
483
+ /* @__PURE__ */ jsx(SuccessFeedback, { message: `${L.FORGOT_PASSWORD_EMAIL} ${form.getValues("email")}` }),
484
+ /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(
485
+ Button,
486
+ {
487
+ onPress: handleResend,
488
+ label: cooldown > 0 ? `${L.RESEND} (${cooldown}s)` : L.RESEND
489
+ }
490
+ ) }),
491
+ /* @__PURE__ */ jsx(View, { style: { alignItems: "center" }, children: /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => router.back(), label: L.GO_BACK }) }) })
492
+ ] })
493
+ ] }) });
494
+ }
495
+ function useLogin() {
496
+ const client = useAuthClient();
497
+ const queryClient = useQueryClient();
498
+ return useMutation({
499
+ mutationFn: async (data) => {
500
+ const response = await client.signIn.email(data);
501
+ if (response.error) {
502
+ throw new RequestFailedError({ message: response.error.message ?? "Login failed" });
503
+ }
504
+ return response.data;
505
+ },
506
+ onSuccess: (data) => {
507
+ if (data?.user) {
508
+ queryClient.setQueryData(authKeys.user(), data.user);
509
+ queryClient.invalidateQueries({ queryKey: authKeys.session() });
510
+ }
511
+ }
512
+ });
513
+ }
514
+ function LoginScreen({
515
+ biometricAvailable = false,
516
+ onBiometricPress,
517
+ onSuccess
518
+ }) {
519
+ const { localization: L } = useAuthLocalization();
520
+ const router = useRouter();
521
+ const loginMutation = useLogin();
522
+ const form = useForm({
523
+ resolver: zodResolver(credentialLoginSchema),
524
+ defaultValues: { identity: "", password: "" },
525
+ mode: "onChange"
526
+ });
527
+ const onSubmit = useCallback(
528
+ (data) => {
529
+ loginMutation.mutate(
530
+ { email: data.identity, password: data.password },
531
+ {
532
+ onSuccess: () => {
533
+ if (onSuccess) {
534
+ onSuccess();
535
+ } else {
536
+ router.replace("/(app)");
537
+ }
538
+ }
539
+ }
540
+ );
541
+ },
542
+ [loginMutation, router, onSuccess]
543
+ );
544
+ return /* @__PURE__ */ jsx(AuthContainer, { children: /* @__PURE__ */ jsxs(View, { style: { gap: 24 }, children: [
545
+ /* @__PURE__ */ jsxs(View, { style: { gap: 4, alignItems: "center" }, children: [
546
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 24, fontWeight: "700", color: "#1f2937" }, children: L.SIGN_IN }),
547
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#6b7280" }, children: L.SIGN_IN_DESCRIPTION })
548
+ ] }),
549
+ /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
550
+ /* @__PURE__ */ jsx(
551
+ Controller,
552
+ {
553
+ control: form.control,
554
+ name: "identity",
555
+ render: ({ field: { onChange, onBlur, value }, fieldState: { error } }) => /* @__PURE__ */ jsx(
556
+ FormInput,
557
+ {
558
+ label: L.SIGN_IN_USERNAME_PLACEHOLDER,
559
+ placeholder: L.EMAIL_PLACEHOLDER,
560
+ autoCapitalize: "none",
561
+ keyboardType: "email-address",
562
+ editable: !loginMutation.isPending,
563
+ onChangeText: onChange,
564
+ onBlur,
565
+ value,
566
+ error: error?.message
567
+ }
568
+ )
569
+ }
570
+ ),
571
+ /* @__PURE__ */ jsx(
572
+ Controller,
573
+ {
574
+ control: form.control,
575
+ name: "password",
576
+ render: ({ field: { onChange, onBlur, value }, fieldState: { error } }) => /* @__PURE__ */ jsx(
577
+ FormPasswordInput,
578
+ {
579
+ label: L.PASSWORD,
580
+ placeholder: L.PASSWORD_PLACEHOLDER,
581
+ editable: !loginMutation.isPending,
582
+ onChangeText: onChange,
583
+ onBlur,
584
+ value,
585
+ error: error?.message
586
+ }
587
+ )
588
+ }
589
+ )
590
+ ] }),
591
+ loginMutation.error && /* @__PURE__ */ jsx(ErrorFeedback, { message: loginMutation.error.message }),
592
+ /* @__PURE__ */ jsx(
593
+ SubmitButton,
594
+ {
595
+ label: L.SIGN_IN_ACTION,
596
+ loading: loginMutation.isPending,
597
+ loadingLabel: L.SIGNING_IN,
598
+ onPress: form.handleSubmit(onSubmit)
599
+ }
600
+ ),
601
+ biometricAvailable && process.env.EXPO_OS === "ios" && onBiometricPress && /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: onBiometricPress }) }),
602
+ /* @__PURE__ */ jsxs(View, { style: { flexDirection: "row", justifyContent: "space-between", marginTop: 8 }, children: [
603
+ /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => router.push("/signup"), label: L.DONT_HAVE_AN_ACCOUNT }) }),
604
+ /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(
605
+ Button,
606
+ {
607
+ onPress: () => router.push("/forgot-password"),
608
+ label: L.FORGOT_PASSWORD_LINK
609
+ }
610
+ ) })
611
+ ] })
612
+ ] }) });
613
+ }
614
+ function MagicLinkScreen({ onSuccess, token }) {
615
+ const { localization: L } = useAuthLocalization();
616
+ const router = useRouter();
617
+ useAuthClient();
618
+ const [state, setState] = useState(
619
+ token ? { step: "verifying", token } : { step: "email" }
620
+ );
621
+ const [cooldown, setCooldown] = useState(0);
622
+ const form = useForm({
623
+ resolver: zodResolver(magicLinkSchema),
624
+ defaultValues: { email: "" },
625
+ mode: "onChange"
626
+ });
627
+ const sendMutation = useMutation({
628
+ mutationFn: async (_data) => {
629
+ throw new NotImplementedError({ feature: "Magic link authentication" });
630
+ }
631
+ });
632
+ const verifyMutation = useMutation({
633
+ mutationFn: async (_verifyToken) => {
634
+ throw new NotImplementedError({ feature: "Magic link verification" });
635
+ }
636
+ });
637
+ const handleSend = useCallback(
638
+ (data) => {
639
+ sendMutation.mutate(data, {
640
+ onSuccess: () => {
641
+ setState({ step: "sent", email: data.email });
642
+ setCooldown(60);
643
+ }
644
+ });
645
+ },
646
+ [sendMutation]
647
+ );
648
+ useEffect(() => {
649
+ if (state.step === "verifying") {
650
+ verifyMutation.mutate(state.token, {
651
+ onSuccess: () => {
652
+ setState({ step: "success" });
653
+ setTimeout(() => {
654
+ if (onSuccess) {
655
+ onSuccess();
656
+ } else {
657
+ router.replace("/(app)");
658
+ }
659
+ }, 2e3);
660
+ },
661
+ onError: (err) => {
662
+ setState({ step: "error", message: err.message });
663
+ }
664
+ });
665
+ }
666
+ }, [state.step]);
667
+ useEffect(() => {
668
+ if (cooldown <= 0) return;
669
+ const timer = setTimeout(() => setCooldown((c) => c - 1), 1e3);
670
+ return () => clearTimeout(timer);
671
+ }, [cooldown]);
672
+ return /* @__PURE__ */ jsx(AuthContainer, { children: /* @__PURE__ */ jsxs(View, { style: { gap: 24 }, children: [
673
+ /* @__PURE__ */ jsxs(View, { style: { gap: 4, alignItems: "center" }, children: [
674
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 24, fontWeight: "700", color: "#1f2937" }, children: L.MAGIC_LINK }),
675
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#6b7280" }, children: L.MAGIC_LINK_DESCRIPTION })
676
+ ] }),
677
+ state.step === "email" && /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
678
+ /* @__PURE__ */ jsx(
679
+ Controller,
680
+ {
681
+ control: form.control,
682
+ name: "email",
683
+ render: ({ field: { onChange, onBlur, value }, fieldState: { error } }) => /* @__PURE__ */ jsx(
684
+ FormInput,
685
+ {
686
+ label: L.EMAIL_PLACEHOLDER,
687
+ placeholder: L.EMAIL_PLACEHOLDER,
688
+ autoCapitalize: "none",
689
+ keyboardType: "email-address",
690
+ editable: !sendMutation.isPending,
691
+ onChangeText: onChange,
692
+ onBlur,
693
+ value,
694
+ error: error?.message
695
+ }
696
+ )
697
+ }
698
+ ),
699
+ sendMutation.error && /* @__PURE__ */ jsx(ErrorFeedback, { message: sendMutation.error.message }),
700
+ /* @__PURE__ */ jsx(
701
+ SubmitButton,
702
+ {
703
+ label: L.MAGIC_LINK_ACTION,
704
+ loading: sendMutation.isPending,
705
+ loadingLabel: L.SENDING,
706
+ onPress: form.handleSubmit(handleSend)
707
+ }
708
+ )
709
+ ] }),
710
+ state.step === "sent" && /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
711
+ /* @__PURE__ */ jsx(SuccessFeedback, { message: `${L.MAGIC_LINK_EMAIL} ${state.email}` }),
712
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#6b7280", textAlign: "center" }, children: L.MAGIC_LINK_HINT }),
713
+ /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(
714
+ Button,
715
+ {
716
+ onPress: () => {
717
+ if (cooldown <= 0) {
718
+ sendMutation.mutate(
719
+ { email: state.email },
720
+ { onSuccess: () => setCooldown(60) }
721
+ );
722
+ }
723
+ },
724
+ label: cooldown > 0 ? `${L.RESEND} (${cooldown}s)` : L.RESEND
725
+ }
726
+ ) })
727
+ ] }),
728
+ state.step === "verifying" && /* @__PURE__ */ jsx(View, { style: { alignItems: "center", gap: 8 }, children: /* @__PURE__ */ jsx(Text, { style: { fontSize: 16, color: "#6b7280" }, children: L.VERIFYING }) }),
729
+ state.step === "success" && /* @__PURE__ */ jsx(SuccessFeedback, { message: L.REDIRECTING }),
730
+ state.step === "error" && /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
731
+ /* @__PURE__ */ jsx(ErrorFeedback, { message: state.message }),
732
+ /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => setState({ step: "email" }), label: L.GO_BACK }) })
733
+ ] })
734
+ ] }) });
735
+ }
736
+ function PhoneLoginScreen({ onSuccess }) {
737
+ const { localization: L } = useAuthLocalization();
738
+ const router = useRouter();
739
+ useAuthClient();
740
+ const [state, setState] = useState({ step: 1 });
741
+ const [cooldown, setCooldown] = useState(0);
742
+ const phoneForm = useForm({
743
+ resolver: zodResolver(phoneLoginSchema),
744
+ defaultValues: { countryCode: "+1", phoneNumber: "" },
745
+ mode: "onChange"
746
+ });
747
+ const otpForm = useForm({
748
+ resolver: zodResolver(otpVerifySchema),
749
+ defaultValues: { code: "" },
750
+ mode: "onChange"
751
+ });
752
+ const sendOtpMutation = useMutation({
753
+ mutationFn: async (_data) => {
754
+ throw new NotImplementedError({ feature: "Phone number authentication" });
755
+ }
756
+ });
757
+ const verifyOtpMutation = useMutation({
758
+ mutationFn: async (_data) => {
759
+ throw new NotImplementedError({ feature: "Phone number verification" });
760
+ }
761
+ });
762
+ const handleSendOtp = useCallback(
763
+ (data) => {
764
+ sendOtpMutation.mutate(data, {
765
+ onSuccess: () => {
766
+ setState({ step: 2, phoneNumber: data.phoneNumber, countryCode: data.countryCode });
767
+ setCooldown(60);
768
+ }
769
+ });
770
+ },
771
+ [sendOtpMutation]
772
+ );
773
+ const handleVerifyOtp = useCallback(
774
+ (data) => {
775
+ if (state.step !== 2) return;
776
+ verifyOtpMutation.mutate(
777
+ { phoneNumber: `${state.countryCode}${state.phoneNumber}`, code: data.code },
778
+ {
779
+ onSuccess: () => {
780
+ if (onSuccess) {
781
+ onSuccess();
782
+ } else {
783
+ router.replace("/(app)");
784
+ }
785
+ }
786
+ }
787
+ );
788
+ },
789
+ [verifyOtpMutation, state, router, onSuccess]
790
+ );
791
+ const handleResend = useCallback(() => {
792
+ if (state.step !== 2 || cooldown > 0) return;
793
+ sendOtpMutation.mutate(
794
+ { countryCode: state.countryCode, phoneNumber: state.phoneNumber },
795
+ { onSuccess: () => setCooldown(60) }
796
+ );
797
+ }, [sendOtpMutation, state, cooldown]);
798
+ useEffect(() => {
799
+ if (cooldown <= 0) return;
800
+ const timer = setTimeout(() => setCooldown((c) => c - 1), 1e3);
801
+ return () => clearTimeout(timer);
802
+ }, [cooldown]);
803
+ const error = sendOtpMutation.error || verifyOtpMutation.error;
804
+ const isPending = sendOtpMutation.isPending || verifyOtpMutation.isPending;
805
+ return /* @__PURE__ */ jsx(AuthContainer, { children: /* @__PURE__ */ jsxs(View, { style: { gap: 24 }, children: [
806
+ /* @__PURE__ */ jsxs(View, { style: { gap: 4, alignItems: "center" }, children: [
807
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 24, fontWeight: "700", color: "#1f2937" }, children: L.SIGN_IN }),
808
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#6b7280" }, children: L.PHONE_NUMBER_PLACEHOLDER })
809
+ ] }),
810
+ state.step === 1 ? /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
811
+ /* @__PURE__ */ jsx(
812
+ Controller,
813
+ {
814
+ control: phoneForm.control,
815
+ name: "countryCode",
816
+ render: ({ field: { onChange, value }, fieldState: { error: fieldError } }) => /* @__PURE__ */ jsx(
817
+ FormInput,
818
+ {
819
+ label: L.COUNTRY_CODE,
820
+ placeholder: "+1",
821
+ keyboardType: "phone-pad",
822
+ editable: !isPending,
823
+ onChangeText: onChange,
824
+ value,
825
+ error: fieldError?.message
826
+ }
827
+ )
828
+ }
829
+ ),
830
+ /* @__PURE__ */ jsx(
831
+ Controller,
832
+ {
833
+ control: phoneForm.control,
834
+ name: "phoneNumber",
835
+ render: ({
836
+ field: { onChange, onBlur, value },
837
+ fieldState: { error: fieldError }
838
+ }) => /* @__PURE__ */ jsx(
839
+ FormInput,
840
+ {
841
+ label: L.PHONE_NUMBER_PLACEHOLDER,
842
+ placeholder: L.PHONE_NUMBER_PLACEHOLDER,
843
+ keyboardType: "phone-pad",
844
+ editable: !isPending,
845
+ onChangeText: onChange,
846
+ onBlur,
847
+ value,
848
+ error: fieldError?.message
849
+ }
850
+ )
851
+ }
852
+ ),
853
+ error && /* @__PURE__ */ jsx(ErrorFeedback, { message: error.message }),
854
+ /* @__PURE__ */ jsx(
855
+ SubmitButton,
856
+ {
857
+ label: L.SEND_VERIFICATION_CODE,
858
+ loading: sendOtpMutation.isPending,
859
+ loadingLabel: L.SENDING_VERIFICATION_CODE,
860
+ onPress: phoneForm.handleSubmit(handleSendOtp)
861
+ }
862
+ )
863
+ ] }) : /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
864
+ /* @__PURE__ */ jsx(
865
+ Controller,
866
+ {
867
+ control: otpForm.control,
868
+ name: "code",
869
+ render: ({
870
+ field: { onChange, onBlur, value },
871
+ fieldState: { error: fieldError }
872
+ }) => /* @__PURE__ */ jsx(
873
+ FormInput,
874
+ {
875
+ label: L.ONE_TIME_PASSWORD,
876
+ placeholder: "000000",
877
+ keyboardType: "number-pad",
878
+ maxLength: 6,
879
+ editable: !isPending,
880
+ onChangeText: onChange,
881
+ onBlur,
882
+ value,
883
+ error: fieldError?.message
884
+ }
885
+ )
886
+ }
887
+ ),
888
+ error && /* @__PURE__ */ jsx(ErrorFeedback, { message: error.message }),
889
+ /* @__PURE__ */ jsx(
890
+ SubmitButton,
891
+ {
892
+ label: L.VERIFY,
893
+ loading: verifyOtpMutation.isPending,
894
+ loadingLabel: L.VERIFYING,
895
+ onPress: otpForm.handleSubmit(handleVerifyOtp)
896
+ }
897
+ ),
898
+ /* @__PURE__ */ jsxs(View, { style: { flexDirection: "row", justifyContent: "space-between" }, children: [
899
+ /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => setState({ step: 1 }), label: L.GO_BACK }) }),
900
+ /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(
901
+ Button,
902
+ {
903
+ onPress: handleResend,
904
+ label: cooldown > 0 ? `${L.RESEND} (${cooldown}s)` : L.RESEND
905
+ }
906
+ ) })
907
+ ] })
908
+ ] })
909
+ ] }) });
910
+ }
911
+ function useResetPassword() {
912
+ const client = useAuthClient();
913
+ return useMutation({
914
+ mutationFn: async (data) => {
915
+ const response = await client.resetPassword(data);
916
+ if (response.error) {
917
+ throw new RequestFailedError({
918
+ message: response.error.message ?? "Password reset failed"
919
+ });
920
+ }
921
+ return true;
922
+ }
923
+ });
924
+ }
925
+ function ResetPasswordScreen({ onSuccess, token }) {
926
+ const { localization: L } = useAuthLocalization();
927
+ const router = useRouter();
928
+ const resetMutation = useResetPassword();
929
+ const form = useForm({
930
+ resolver: zodResolver(resetPasswordSchema),
931
+ defaultValues: { newPassword: "", confirmPassword: "" },
932
+ mode: "onChange"
933
+ });
934
+ const handleSubmit = useCallback(
935
+ (data) => {
936
+ if (!token) return;
937
+ resetMutation.mutate(
938
+ { newPassword: data.newPassword, token },
939
+ {
940
+ onSuccess: () => {
941
+ if (onSuccess) {
942
+ onSuccess();
943
+ } else {
944
+ router.replace("/login");
945
+ }
946
+ }
947
+ }
948
+ );
949
+ },
950
+ [resetMutation, token, router, onSuccess]
951
+ );
952
+ if (!token) {
953
+ return /* @__PURE__ */ jsx(AuthContainer, { children: /* @__PURE__ */ jsxs(View, { style: { gap: 24 }, children: [
954
+ /* @__PURE__ */ jsx(View, { style: { gap: 4, alignItems: "center" }, children: /* @__PURE__ */ jsx(Text, { style: { fontSize: 24, fontWeight: "700", color: "#1f2937" }, children: L.RESET_PASSWORD }) }),
955
+ /* @__PURE__ */ jsx(ErrorFeedback, { message: L.REQUEST_FAILED }),
956
+ /* @__PURE__ */ jsx(View, { style: { alignItems: "center" }, children: /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => router.back(), label: L.GO_BACK }) }) })
957
+ ] }) });
958
+ }
959
+ if (resetMutation.isSuccess) {
960
+ return /* @__PURE__ */ jsx(AuthContainer, { children: /* @__PURE__ */ jsxs(View, { style: { gap: 24 }, children: [
961
+ /* @__PURE__ */ jsx(SuccessFeedback, { message: L.RESET_PASSWORD_SUCCESS }),
962
+ /* @__PURE__ */ jsx(View, { style: { alignItems: "center" }, children: /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => router.replace("/login"), label: L.SIGN_IN }) }) })
963
+ ] }) });
964
+ }
965
+ return /* @__PURE__ */ jsx(AuthContainer, { children: /* @__PURE__ */ jsxs(View, { style: { gap: 24 }, children: [
966
+ /* @__PURE__ */ jsx(View, { style: { gap: 4, alignItems: "center" }, children: /* @__PURE__ */ jsx(Text, { style: { fontSize: 24, fontWeight: "700", color: "#1f2937" }, children: L.RESET_PASSWORD }) }),
967
+ /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
968
+ /* @__PURE__ */ jsx(
969
+ Controller,
970
+ {
971
+ control: form.control,
972
+ name: "newPassword",
973
+ render: ({ field: { onChange, onBlur, value }, fieldState: { error } }) => /* @__PURE__ */ jsx(
974
+ FormPasswordInput,
975
+ {
976
+ label: L.NEW_PASSWORD,
977
+ placeholder: L.PASSWORD_PLACEHOLDER,
978
+ editable: !resetMutation.isPending,
979
+ onChangeText: onChange,
980
+ onBlur,
981
+ value,
982
+ error: error?.message
983
+ }
984
+ )
985
+ }
986
+ ),
987
+ /* @__PURE__ */ jsx(
988
+ Controller,
989
+ {
990
+ control: form.control,
991
+ name: "confirmPassword",
992
+ render: ({ field: { onChange, onBlur, value }, fieldState: { error } }) => /* @__PURE__ */ jsx(
993
+ FormPasswordInput,
994
+ {
995
+ label: L.CONFIRM_NEW_PASSWORD,
996
+ placeholder: L.CONFIRM_PASSWORD_PLACEHOLDER,
997
+ editable: !resetMutation.isPending,
998
+ onChangeText: onChange,
999
+ onBlur,
1000
+ value,
1001
+ error: error?.message
1002
+ }
1003
+ )
1004
+ }
1005
+ )
1006
+ ] }),
1007
+ resetMutation.error && /* @__PURE__ */ jsx(ErrorFeedback, { message: resetMutation.error.message }),
1008
+ /* @__PURE__ */ jsx(
1009
+ SubmitButton,
1010
+ {
1011
+ label: L.RESET_PASSWORD_ACTION,
1012
+ loading: resetMutation.isPending,
1013
+ loadingLabel: L.LOADING,
1014
+ onPress: form.handleSubmit(handleSubmit)
1015
+ }
1016
+ ),
1017
+ /* @__PURE__ */ jsx(View, { style: { alignItems: "center", marginTop: 8 }, children: /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => router.back(), label: L.GO_BACK }) }) })
1018
+ ] }) });
1019
+ }
1020
+ function useSignup() {
1021
+ const client = useAuthClient();
1022
+ const queryClient = useQueryClient();
1023
+ return useMutation({
1024
+ mutationFn: async (data) => {
1025
+ const response = await client.signUp.email(data);
1026
+ if (response.error) {
1027
+ throw new RequestFailedError({ message: response.error.message ?? "Signup failed" });
1028
+ }
1029
+ return response.data;
1030
+ },
1031
+ onSuccess: (data) => {
1032
+ if (data?.user) {
1033
+ queryClient.setQueryData(authKeys.user(), data.user);
1034
+ queryClient.invalidateQueries({ queryKey: authKeys.session() });
1035
+ }
1036
+ }
1037
+ });
1038
+ }
1039
+ function SignupScreen({ onSuccess }) {
1040
+ const { localization: L } = useAuthLocalization();
1041
+ const router = useRouter();
1042
+ const signupMutation = useSignup();
1043
+ const form = useForm({
1044
+ resolver: zodResolver(signupSchema),
1045
+ defaultValues: {
1046
+ email: "",
1047
+ name: "",
1048
+ password: "",
1049
+ acceptTerms: true
1050
+ },
1051
+ mode: "onChange"
1052
+ });
1053
+ const onSubmit = useCallback(
1054
+ (data) => {
1055
+ signupMutation.mutate(data, {
1056
+ onSuccess: () => {
1057
+ if (onSuccess) {
1058
+ onSuccess();
1059
+ } else {
1060
+ router.replace("/(app)");
1061
+ }
1062
+ }
1063
+ });
1064
+ },
1065
+ [signupMutation, router, onSuccess]
1066
+ );
1067
+ return /* @__PURE__ */ jsx(AuthContainer, { children: /* @__PURE__ */ jsxs(View, { style: { gap: 24 }, children: [
1068
+ /* @__PURE__ */ jsxs(View, { style: { gap: 4, alignItems: "center" }, children: [
1069
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 24, fontWeight: "700", color: "#1f2937" }, children: L.SIGN_UP }),
1070
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#6b7280" }, children: L.SIGN_UP_DESCRIPTION })
1071
+ ] }),
1072
+ /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
1073
+ /* @__PURE__ */ jsx(
1074
+ Controller,
1075
+ {
1076
+ control: form.control,
1077
+ name: "name",
1078
+ render: ({ field: { onChange, onBlur, value }, fieldState: { error } }) => /* @__PURE__ */ jsx(
1079
+ FormInput,
1080
+ {
1081
+ label: L.NAME_PLACEHOLDER,
1082
+ placeholder: L.NAME_PLACEHOLDER,
1083
+ autoCapitalize: "words",
1084
+ editable: !signupMutation.isPending,
1085
+ onChangeText: onChange,
1086
+ onBlur,
1087
+ value,
1088
+ error: error?.message
1089
+ }
1090
+ )
1091
+ }
1092
+ ),
1093
+ /* @__PURE__ */ jsx(
1094
+ Controller,
1095
+ {
1096
+ control: form.control,
1097
+ name: "email",
1098
+ render: ({ field: { onChange, onBlur, value }, fieldState: { error } }) => /* @__PURE__ */ jsx(
1099
+ FormInput,
1100
+ {
1101
+ label: L.EMAIL_PLACEHOLDER,
1102
+ placeholder: L.EMAIL_PLACEHOLDER,
1103
+ autoCapitalize: "none",
1104
+ keyboardType: "email-address",
1105
+ editable: !signupMutation.isPending,
1106
+ onChangeText: onChange,
1107
+ onBlur,
1108
+ value,
1109
+ error: error?.message
1110
+ }
1111
+ )
1112
+ }
1113
+ ),
1114
+ /* @__PURE__ */ jsx(
1115
+ Controller,
1116
+ {
1117
+ control: form.control,
1118
+ name: "password",
1119
+ render: ({ field: { onChange, onBlur, value }, fieldState: { error } }) => /* @__PURE__ */ jsx(
1120
+ FormPasswordInput,
1121
+ {
1122
+ label: L.PASSWORD,
1123
+ placeholder: L.PASSWORD_PLACEHOLDER,
1124
+ editable: !signupMutation.isPending,
1125
+ onChangeText: onChange,
1126
+ onBlur,
1127
+ value,
1128
+ error: error?.message
1129
+ }
1130
+ )
1131
+ }
1132
+ )
1133
+ ] }),
1134
+ signupMutation.error && /* @__PURE__ */ jsx(ErrorFeedback, { message: signupMutation.error.message }),
1135
+ /* @__PURE__ */ jsx(
1136
+ SubmitButton,
1137
+ {
1138
+ label: L.SIGN_UP_ACTION,
1139
+ loading: signupMutation.isPending,
1140
+ loadingLabel: L.SIGNING_UP,
1141
+ onPress: form.handleSubmit(onSubmit)
1142
+ }
1143
+ ),
1144
+ /* @__PURE__ */ jsx(View, { style: { alignItems: "center", marginTop: 8 }, children: /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => router.push("/login"), label: L.ALREADY_HAVE_AN_ACCOUNT }) }) })
1145
+ ] }) });
1146
+ }
1147
+ function TwoFactorScreen({ onSuccess }) {
1148
+ const { localization: L } = useAuthLocalization();
1149
+ const router = useRouter();
1150
+ useAuthClient();
1151
+ const [mode, setMode] = useState("totp");
1152
+ const totpForm = useForm({
1153
+ resolver: zodResolver(totpSchema),
1154
+ defaultValues: { code: "" },
1155
+ mode: "onChange"
1156
+ });
1157
+ const backupForm = useForm({
1158
+ resolver: zodResolver(backupCodeSchema),
1159
+ defaultValues: { backupCode: "" },
1160
+ mode: "onChange"
1161
+ });
1162
+ const verifyMutation = useMutation({
1163
+ mutationFn: async (_data) => {
1164
+ throw new NotImplementedError({ feature: "Two-factor verification" });
1165
+ }
1166
+ });
1167
+ const handleTotpSubmit = useCallback(
1168
+ (data) => {
1169
+ verifyMutation.mutate(
1170
+ { code: data.code },
1171
+ {
1172
+ onSuccess: () => {
1173
+ if (onSuccess) {
1174
+ onSuccess();
1175
+ } else {
1176
+ router.replace("/(app)");
1177
+ }
1178
+ }
1179
+ }
1180
+ );
1181
+ },
1182
+ [verifyMutation, router, onSuccess]
1183
+ );
1184
+ const handleBackupSubmit = useCallback(
1185
+ (data) => {
1186
+ verifyMutation.mutate(
1187
+ { backupCode: data.backupCode },
1188
+ {
1189
+ onSuccess: () => {
1190
+ if (onSuccess) {
1191
+ onSuccess();
1192
+ } else {
1193
+ router.replace("/(app)");
1194
+ }
1195
+ }
1196
+ }
1197
+ );
1198
+ },
1199
+ [verifyMutation, router, onSuccess]
1200
+ );
1201
+ return /* @__PURE__ */ jsx(AuthContainer, { children: /* @__PURE__ */ jsxs(View, { style: { gap: 24 }, children: [
1202
+ /* @__PURE__ */ jsxs(View, { style: { gap: 4, alignItems: "center" }, children: [
1203
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 24, fontWeight: "700", color: "#1f2937" }, children: L.TWO_FACTOR_PROMPT }),
1204
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#6b7280" }, children: L.TWO_FACTOR_DESCRIPTION })
1205
+ ] }),
1206
+ /* @__PURE__ */ jsxs(View, { style: { flexDirection: "row", gap: 8 }, children: [
1207
+ /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => setMode("totp"), label: L.USE_AUTHENTICATOR }) }),
1208
+ /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => setMode("backup"), label: L.USE_BACKUP_CODE }) })
1209
+ ] }),
1210
+ mode === "totp" ? /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
1211
+ /* @__PURE__ */ jsx(
1212
+ Controller,
1213
+ {
1214
+ control: totpForm.control,
1215
+ name: "code",
1216
+ render: ({ field: { onChange, onBlur, value }, fieldState: { error } }) => /* @__PURE__ */ jsx(
1217
+ FormInput,
1218
+ {
1219
+ label: L.ENTER_TOTP_CODE,
1220
+ placeholder: "000000",
1221
+ keyboardType: "number-pad",
1222
+ maxLength: 6,
1223
+ editable: !verifyMutation.isPending,
1224
+ onChangeText: onChange,
1225
+ onBlur,
1226
+ value,
1227
+ error: error?.message
1228
+ }
1229
+ )
1230
+ }
1231
+ ),
1232
+ verifyMutation.error && /* @__PURE__ */ jsx(ErrorFeedback, { message: verifyMutation.error.message }),
1233
+ /* @__PURE__ */ jsx(
1234
+ SubmitButton,
1235
+ {
1236
+ label: L.TWO_FACTOR_ACTION,
1237
+ loading: verifyMutation.isPending,
1238
+ loadingLabel: L.VERIFYING,
1239
+ onPress: totpForm.handleSubmit(handleTotpSubmit)
1240
+ }
1241
+ )
1242
+ ] }) : /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
1243
+ /* @__PURE__ */ jsx(
1244
+ Controller,
1245
+ {
1246
+ control: backupForm.control,
1247
+ name: "backupCode",
1248
+ render: ({ field: { onChange, onBlur, value }, fieldState: { error } }) => /* @__PURE__ */ jsx(
1249
+ FormInput,
1250
+ {
1251
+ label: L.BACKUP_CODE_PLACEHOLDER,
1252
+ placeholder: L.BACKUP_CODE_PLACEHOLDER,
1253
+ autoCapitalize: "none",
1254
+ editable: !verifyMutation.isPending,
1255
+ onChangeText: onChange,
1256
+ onBlur,
1257
+ value,
1258
+ error: error?.message
1259
+ }
1260
+ )
1261
+ }
1262
+ ),
1263
+ verifyMutation.error && /* @__PURE__ */ jsx(ErrorFeedback, { message: verifyMutation.error.message }),
1264
+ /* @__PURE__ */ jsx(
1265
+ SubmitButton,
1266
+ {
1267
+ label: L.TWO_FACTOR_ACTION,
1268
+ loading: verifyMutation.isPending,
1269
+ loadingLabel: L.VERIFYING,
1270
+ onPress: backupForm.handleSubmit(handleBackupSubmit)
1271
+ }
1272
+ )
1273
+ ] })
1274
+ ] }) });
1275
+ }
1276
+ function AuthView({
1277
+ route = "login",
1278
+ onSuccess,
1279
+ token,
1280
+ biometricAvailable,
1281
+ onBiometricPress,
1282
+ fallback = null
1283
+ }) {
1284
+ switch (route) {
1285
+ case "login":
1286
+ return /* @__PURE__ */ jsx(
1287
+ LoginScreen,
1288
+ {
1289
+ onSuccess,
1290
+ biometricAvailable,
1291
+ onBiometricPress
1292
+ }
1293
+ );
1294
+ case "signup":
1295
+ return /* @__PURE__ */ jsx(SignupScreen, { onSuccess });
1296
+ case "phone-login":
1297
+ return /* @__PURE__ */ jsx(PhoneLoginScreen, { onSuccess });
1298
+ case "magic-link":
1299
+ return /* @__PURE__ */ jsx(MagicLinkScreen, { onSuccess, token });
1300
+ case "forgot-password":
1301
+ return /* @__PURE__ */ jsx(ForgotPasswordScreen, { onSuccess });
1302
+ case "reset-password":
1303
+ return /* @__PURE__ */ jsx(ResetPasswordScreen, { onSuccess, token });
1304
+ case "verify-email":
1305
+ return /* @__PURE__ */ jsx(EmailVerificationScreen, { onSuccess, token });
1306
+ case "two-factor":
1307
+ return /* @__PURE__ */ jsx(TwoFactorScreen, { onSuccess });
1308
+ default:
1309
+ return /* @__PURE__ */ jsx(Fragment, { children: fallback });
1310
+ }
1311
+ }
1312
+ function useUser() {
1313
+ const client = useAuthClient();
1314
+ return useQuery({
1315
+ queryKey: authKeys.user(),
1316
+ queryFn: async () => {
1317
+ const response = await client.getSession();
1318
+ if (response.error) return null;
1319
+ return response.data?.user ?? null;
1320
+ },
1321
+ staleTime: 5 * 60 * 1e3
1322
+ });
1323
+ }
1324
+ function ChangeEmailScreen({ onSuccess, redirectUrl }) {
1325
+ const { localization: L } = useAuthLocalization();
1326
+ const router = useRouter();
1327
+ const client = useAuthClient();
1328
+ const { data: user } = useUser();
1329
+ const [emailSent, setEmailSent] = useState(false);
1330
+ const [cooldown, setCooldown] = useState(0);
1331
+ const form = useForm({
1332
+ resolver: zodResolver(changeEmailSchema),
1333
+ defaultValues: { email: "" },
1334
+ mode: "onChange"
1335
+ });
1336
+ const sendMutation = useMutation({
1337
+ mutationFn: async (data) => {
1338
+ const response = await client.changeEmail({
1339
+ newEmail: data.email,
1340
+ callbackURL: redirectUrl
1341
+ });
1342
+ if (response.error) {
1343
+ throw new RequestFailedError({ message: response.error.message ?? L.REQUEST_FAILED });
1344
+ }
1345
+ return response.data;
1346
+ }
1347
+ });
1348
+ const handleSubmit = useCallback(
1349
+ (data) => {
1350
+ sendMutation.mutate(data, {
1351
+ onSuccess: () => {
1352
+ setEmailSent(true);
1353
+ setCooldown(60);
1354
+ onSuccess?.();
1355
+ }
1356
+ });
1357
+ },
1358
+ [sendMutation, onSuccess]
1359
+ );
1360
+ const handleResend = useCallback(() => {
1361
+ if (cooldown > 0) return;
1362
+ const email = form.getValues("email");
1363
+ sendMutation.mutate({ email }, { onSuccess: () => setCooldown(60) });
1364
+ }, [sendMutation, cooldown, form]);
1365
+ useEffect(() => {
1366
+ if (cooldown <= 0) return;
1367
+ const timer = setTimeout(() => setCooldown((c) => c - 1), 1e3);
1368
+ return () => clearTimeout(timer);
1369
+ }, [cooldown]);
1370
+ return /* @__PURE__ */ jsx(AuthContainer, { children: /* @__PURE__ */ jsxs(View, { style: { gap: 24 }, children: [
1371
+ /* @__PURE__ */ jsxs(View, { style: { gap: 4, alignItems: "center" }, children: [
1372
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 24, fontWeight: "700", color: "#1f2937" }, children: L.CHANGE_EMAIL }),
1373
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#6b7280" }, children: L.CHANGE_EMAIL_DESCRIPTION })
1374
+ ] }),
1375
+ user?.email && /* @__PURE__ */ jsxs(View, { style: { paddingVertical: 8 }, children: [
1376
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 12, color: "#9ca3af" }, children: L.CURRENT_EMAIL }),
1377
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 16, color: "#6b7280" }, children: user.email })
1378
+ ] }),
1379
+ !emailSent ? /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
1380
+ /* @__PURE__ */ jsx(
1381
+ Controller,
1382
+ {
1383
+ control: form.control,
1384
+ name: "email",
1385
+ render: ({ field: { onChange, onBlur, value }, fieldState: { error } }) => /* @__PURE__ */ jsx(
1386
+ FormInput,
1387
+ {
1388
+ label: L.NEW_EMAIL_PLACEHOLDER,
1389
+ placeholder: L.EMAIL_PLACEHOLDER,
1390
+ autoCapitalize: "none",
1391
+ keyboardType: "email-address",
1392
+ editable: !sendMutation.isPending,
1393
+ onChangeText: onChange,
1394
+ onBlur,
1395
+ value,
1396
+ error: error?.message
1397
+ }
1398
+ )
1399
+ }
1400
+ ),
1401
+ sendMutation.error && /* @__PURE__ */ jsx(ErrorFeedback, { message: sendMutation.error.message }),
1402
+ /* @__PURE__ */ jsx(
1403
+ SubmitButton,
1404
+ {
1405
+ label: L.CHANGE_EMAIL,
1406
+ loading: sendMutation.isPending,
1407
+ loadingLabel: L.SENDING,
1408
+ onPress: form.handleSubmit(handleSubmit)
1409
+ }
1410
+ ),
1411
+ /* @__PURE__ */ jsx(View, { style: { alignItems: "center", marginTop: 8 }, children: /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => router.back(), label: L.GO_BACK }) }) })
1412
+ ] }) : /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
1413
+ /* @__PURE__ */ jsx(SuccessFeedback, { message: L.CHANGE_EMAIL_SENT }),
1414
+ /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(
1415
+ Button,
1416
+ {
1417
+ onPress: handleResend,
1418
+ label: cooldown > 0 ? `${L.RESEND} (${cooldown}s)` : L.RESEND
1419
+ }
1420
+ ) }),
1421
+ /* @__PURE__ */ jsx(View, { style: { alignItems: "center" }, children: /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => router.back(), label: L.GO_BACK }) }) })
1422
+ ] })
1423
+ ] }) });
1424
+ }
1425
+ function ChangePasswordScreen({ onSuccess }) {
1426
+ const { localization: L } = useAuthLocalization();
1427
+ const router = useRouter();
1428
+ const client = useAuthClient();
1429
+ const [showSuccess, setShowSuccess] = useState(false);
1430
+ const form = useForm({
1431
+ resolver: zodResolver(changePasswordSchema),
1432
+ defaultValues: { currentPassword: "", newPassword: "", confirmPassword: "" },
1433
+ mode: "onChange"
1434
+ });
1435
+ const changeMutation = useMutation({
1436
+ mutationFn: async (data) => {
1437
+ const response = await client.changePassword({
1438
+ currentPassword: data.currentPassword,
1439
+ newPassword: data.newPassword
1440
+ });
1441
+ if (response.error) {
1442
+ throw new RequestFailedError({ message: response.error.message ?? L.REQUEST_FAILED });
1443
+ }
1444
+ return response.data;
1445
+ }
1446
+ });
1447
+ const handleSubmit = useCallback(
1448
+ (data) => {
1449
+ changeMutation.mutate(data, {
1450
+ onSuccess: () => {
1451
+ form.reset();
1452
+ setShowSuccess(true);
1453
+ setTimeout(() => setShowSuccess(false), 3e3);
1454
+ onSuccess?.();
1455
+ }
1456
+ });
1457
+ },
1458
+ [changeMutation, form, onSuccess]
1459
+ );
1460
+ return /* @__PURE__ */ jsx(AuthContainer, { children: /* @__PURE__ */ jsxs(View, { style: { gap: 24 }, children: [
1461
+ /* @__PURE__ */ jsxs(View, { style: { gap: 4, alignItems: "center" }, children: [
1462
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 24, fontWeight: "700", color: "#1f2937" }, children: L.CHANGE_PASSWORD }),
1463
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#6b7280" }, children: L.CHANGE_PASSWORD_DESCRIPTION })
1464
+ ] }),
1465
+ showSuccess ? /* @__PURE__ */ jsx(SuccessFeedback, { message: L.CHANGE_PASSWORD_SUCCESS }) : /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
1466
+ /* @__PURE__ */ jsx(
1467
+ Controller,
1468
+ {
1469
+ control: form.control,
1470
+ name: "currentPassword",
1471
+ render: ({ field: { onChange, onBlur, value }, fieldState: { error } }) => /* @__PURE__ */ jsx(
1472
+ FormPasswordInput,
1473
+ {
1474
+ label: L.CURRENT_PASSWORD,
1475
+ placeholder: L.CURRENT_PASSWORD,
1476
+ editable: !changeMutation.isPending,
1477
+ onChangeText: onChange,
1478
+ onBlur,
1479
+ value,
1480
+ error: error?.message
1481
+ }
1482
+ )
1483
+ }
1484
+ ),
1485
+ /* @__PURE__ */ jsx(
1486
+ Controller,
1487
+ {
1488
+ control: form.control,
1489
+ name: "newPassword",
1490
+ render: ({ field: { onChange, onBlur, value }, fieldState: { error } }) => /* @__PURE__ */ jsx(
1491
+ FormPasswordInput,
1492
+ {
1493
+ label: L.NEW_PASSWORD,
1494
+ placeholder: L.NEW_PASSWORD,
1495
+ editable: !changeMutation.isPending,
1496
+ onChangeText: onChange,
1497
+ onBlur,
1498
+ value,
1499
+ error: error?.message
1500
+ }
1501
+ )
1502
+ }
1503
+ ),
1504
+ /* @__PURE__ */ jsx(
1505
+ Controller,
1506
+ {
1507
+ control: form.control,
1508
+ name: "confirmPassword",
1509
+ render: ({ field: { onChange, onBlur, value }, fieldState: { error } }) => /* @__PURE__ */ jsx(
1510
+ FormPasswordInput,
1511
+ {
1512
+ label: L.CONFIRM_NEW_PASSWORD,
1513
+ placeholder: L.CONFIRM_NEW_PASSWORD,
1514
+ editable: !changeMutation.isPending,
1515
+ onChangeText: onChange,
1516
+ onBlur,
1517
+ value,
1518
+ error: error?.message
1519
+ }
1520
+ )
1521
+ }
1522
+ ),
1523
+ changeMutation.error && /* @__PURE__ */ jsx(ErrorFeedback, { message: changeMutation.error.message }),
1524
+ /* @__PURE__ */ jsx(
1525
+ SubmitButton,
1526
+ {
1527
+ label: L.CHANGE_PASSWORD,
1528
+ loading: changeMutation.isPending,
1529
+ loadingLabel: L.CHANGING_PASSWORD,
1530
+ onPress: form.handleSubmit(handleSubmit)
1531
+ }
1532
+ ),
1533
+ /* @__PURE__ */ jsx(View, { style: { alignItems: "center", marginTop: 8 }, children: /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => router.back(), label: L.GO_BACK }) }) })
1534
+ ] })
1535
+ ] }) });
1536
+ }
1537
+ function DeleteAccountScreen({
1538
+ confirmationPhrase = "delete my account",
1539
+ onSuccess
1540
+ }) {
1541
+ const { localization: L } = useAuthLocalization();
1542
+ const router = useRouter();
1543
+ const client = useAuthClient();
1544
+ const [phrase, setPhrase] = useState("");
1545
+ const [password, setPassword] = useState("");
1546
+ const phraseMatches = phrase === confirmationPhrase;
1547
+ const deleteMutation = useMutation({
1548
+ mutationFn: async (pw) => {
1549
+ const response = await client.deleteUser({ password: pw });
1550
+ if (response.error) {
1551
+ throw new RequestFailedError({ message: response.error.message ?? L.REQUEST_FAILED });
1552
+ }
1553
+ return response.data;
1554
+ }
1555
+ });
1556
+ const handleDelete = useCallback(() => {
1557
+ if (!phraseMatches || !password) return;
1558
+ deleteMutation.mutate(password, {
1559
+ onSuccess: () => {
1560
+ if (onSuccess) {
1561
+ onSuccess();
1562
+ } else {
1563
+ router.replace("/sign-in");
1564
+ }
1565
+ }
1566
+ });
1567
+ }, [phraseMatches, password, deleteMutation, onSuccess, router]);
1568
+ return /* @__PURE__ */ jsx(AuthContainer, { children: /* @__PURE__ */ jsxs(View, { style: { gap: 24 }, children: [
1569
+ /* @__PURE__ */ jsxs(View, { style: { gap: 4, alignItems: "center" }, children: [
1570
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 24, fontWeight: "700", color: "#dc2626" }, children: L.DELETE_ACCOUNT }),
1571
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#dc2626" }, children: L.DELETE_ACCOUNT_WARNING })
1572
+ ] }),
1573
+ /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
1574
+ /* @__PURE__ */ jsxs(View, { style: { gap: 4 }, children: [
1575
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#374151" }, children: L.TYPE_TO_CONFIRM.replace("{{phrase}}", confirmationPhrase) }),
1576
+ /* @__PURE__ */ jsx(
1577
+ TextInput,
1578
+ {
1579
+ value: phrase,
1580
+ onChangeText: setPhrase,
1581
+ placeholder: confirmationPhrase,
1582
+ editable: !deleteMutation.isPending,
1583
+ autoCapitalize: "none",
1584
+ style: {
1585
+ borderWidth: 1,
1586
+ borderColor: phraseMatches ? "#16a34a" : "#d1d5db",
1587
+ borderRadius: 8,
1588
+ padding: 12,
1589
+ fontSize: 16
1590
+ }
1591
+ }
1592
+ )
1593
+ ] }),
1594
+ phraseMatches && /* @__PURE__ */ jsxs(View, { style: { gap: 4 }, children: [
1595
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#374151" }, children: L.PASSWORD }),
1596
+ /* @__PURE__ */ jsx(
1597
+ TextInput,
1598
+ {
1599
+ value: password,
1600
+ onChangeText: setPassword,
1601
+ placeholder: L.PASSWORD_PLACEHOLDER,
1602
+ secureTextEntry: true,
1603
+ editable: !deleteMutation.isPending,
1604
+ style: {
1605
+ borderWidth: 1,
1606
+ borderColor: "#d1d5db",
1607
+ borderRadius: 8,
1608
+ padding: 12,
1609
+ fontSize: 16
1610
+ }
1611
+ }
1612
+ )
1613
+ ] }),
1614
+ deleteMutation.error && /* @__PURE__ */ jsx(ErrorFeedback, { message: deleteMutation.error.message }),
1615
+ /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(
1616
+ Button,
1617
+ {
1618
+ onPress: handleDelete,
1619
+ label: deleteMutation.isPending ? L.DELETING_ACCOUNT : L.DELETE
1620
+ }
1621
+ ) }),
1622
+ /* @__PURE__ */ jsx(View, { style: { alignItems: "center", marginTop: 8 }, children: /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => router.back(), label: L.GO_BACK }) }) })
1623
+ ] })
1624
+ ] }) });
1625
+ }
1626
+ function formatLastActive(dateStr, L) {
1627
+ if (!dateStr) return "";
1628
+ const date = new Date(dateStr);
1629
+ const now = /* @__PURE__ */ new Date();
1630
+ const diffMs = now.getTime() - date.getTime();
1631
+ const diffMin = Math.floor(diffMs / 6e4);
1632
+ if (diffMin < 1) return L.JUST_NOW;
1633
+ if (diffMin < 60) return L.MINUTES_AGO.replace("{{n}}", String(diffMin));
1634
+ const diffHrs = Math.floor(diffMin / 60);
1635
+ if (diffHrs < 24) return L.HOURS_AGO.replace("{{n}}", String(diffHrs));
1636
+ const diffDays = Math.floor(diffHrs / 24);
1637
+ return L.DAYS_AGO.replace("{{n}}", String(diffDays));
1638
+ }
1639
+ function SessionsScreen({ onSuccess }) {
1640
+ const { localization: L } = useAuthLocalization();
1641
+ const router = useRouter();
1642
+ const client = useAuthClient();
1643
+ const queryClient = useQueryClient();
1644
+ const sessionsQuery = useQuery({
1645
+ queryKey: authKeys.sessions(),
1646
+ queryFn: async () => {
1647
+ const response = await client.listSessions();
1648
+ if (response.error) {
1649
+ throw new RequestFailedError({ message: response.error.message ?? L.REQUEST_FAILED });
1650
+ }
1651
+ return response.data ?? [];
1652
+ }
1653
+ });
1654
+ const revokeMutation = useMutation({
1655
+ mutationFn: async (sessionToken) => {
1656
+ const response = await client.revokeSession({ token: sessionToken });
1657
+ if (response.error) {
1658
+ throw new RequestFailedError({ message: response.error.message ?? L.REQUEST_FAILED });
1659
+ }
1660
+ return response.data;
1661
+ },
1662
+ onSuccess: () => {
1663
+ queryClient.invalidateQueries({ queryKey: authKeys.sessions() });
1664
+ }
1665
+ });
1666
+ const revokeAllMutation = useMutation({
1667
+ mutationFn: async () => {
1668
+ const response = await client.revokeOtherSessions();
1669
+ if (response.error) {
1670
+ throw new RequestFailedError({ message: response.error.message ?? L.REQUEST_FAILED });
1671
+ }
1672
+ return response.data;
1673
+ },
1674
+ onSuccess: () => {
1675
+ queryClient.invalidateQueries({ queryKey: authKeys.sessions() });
1676
+ onSuccess?.();
1677
+ }
1678
+ });
1679
+ const handleRevoke = useCallback(
1680
+ (token) => {
1681
+ Alert.alert(L.REVOKE_SESSION, L.ARE_YOU_SURE, [
1682
+ { text: L.CANCEL, style: "cancel" },
1683
+ {
1684
+ text: L.CONFIRM,
1685
+ style: "destructive",
1686
+ onPress: () => revokeMutation.mutate(token)
1687
+ }
1688
+ ]);
1689
+ },
1690
+ [L, revokeMutation]
1691
+ );
1692
+ const handleRevokeAll = useCallback(() => {
1693
+ Alert.alert(L.REVOKE_ALL_OTHER_SESSIONS, L.ARE_YOU_SURE, [
1694
+ { text: L.CANCEL, style: "cancel" },
1695
+ {
1696
+ text: L.CONFIRM,
1697
+ style: "destructive",
1698
+ onPress: () => revokeAllMutation.mutate()
1699
+ }
1700
+ ]);
1701
+ }, [L, revokeAllMutation]);
1702
+ const sessions = sessionsQuery.data ?? [];
1703
+ revokeMutation.isPending || revokeAllMutation.isPending;
1704
+ return /* @__PURE__ */ jsx(AuthContainer, { children: /* @__PURE__ */ jsxs(View, { style: { gap: 24, flex: 1 }, children: [
1705
+ /* @__PURE__ */ jsxs(View, { style: { gap: 4, alignItems: "center" }, children: [
1706
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 24, fontWeight: "700", color: "#1f2937" }, children: L.SESSIONS }),
1707
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#6b7280" }, children: L.SESSIONS_DESCRIPTION })
1708
+ ] }),
1709
+ sessionsQuery.isLoading && /* @__PURE__ */ jsx(ActivityIndicator, { size: "large" }),
1710
+ sessionsQuery.error && /* @__PURE__ */ jsx(ErrorFeedback, { message: sessionsQuery.error.message }),
1711
+ !sessionsQuery.isLoading && sessions.length === 0 && /* @__PURE__ */ jsx(Text, { style: { textAlign: "center", color: "#6b7280" }, children: L.NO_OTHER_SESSIONS }),
1712
+ sessions.length > 0 && /* @__PURE__ */ jsx(
1713
+ FlatList,
1714
+ {
1715
+ data: sessions,
1716
+ keyExtractor: (item) => item.token ?? item.id,
1717
+ refreshControl: /* @__PURE__ */ jsx(
1718
+ RefreshControl,
1719
+ {
1720
+ refreshing: sessionsQuery.isRefetching,
1721
+ onRefresh: () => sessionsQuery.refetch()
1722
+ }
1723
+ ),
1724
+ renderItem: ({ item }) => /* @__PURE__ */ jsxs(
1725
+ View,
1726
+ {
1727
+ style: {
1728
+ flexDirection: "row",
1729
+ justifyContent: "space-between",
1730
+ alignItems: "center",
1731
+ paddingVertical: 12,
1732
+ borderBottomWidth: 1,
1733
+ borderBottomColor: "#f3f4f6"
1734
+ },
1735
+ children: [
1736
+ /* @__PURE__ */ jsxs(View, { style: { flex: 1, gap: 2 }, children: [
1737
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 16, fontWeight: "600", color: "#1f2937" }, children: item.userAgent ?? L.UNKNOWN }),
1738
+ /* @__PURE__ */ jsxs(Text, { style: { fontSize: 12, color: "#9ca3af" }, children: [
1739
+ item.ipAddress ?? "",
1740
+ item.updatedAt ? ` \xB7 ${formatLastActive(item.updatedAt, L)}` : ""
1741
+ ] }),
1742
+ item.current && /* @__PURE__ */ jsx(Text, { style: { fontSize: 11, fontWeight: "600", color: "#16a34a" }, children: L.CURRENT_DEVICE })
1743
+ ] }),
1744
+ !item.current && /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(
1745
+ Button,
1746
+ {
1747
+ onPress: () => handleRevoke(item.token ?? item.id),
1748
+ label: L.REVOKE_SESSION
1749
+ }
1750
+ ) })
1751
+ ]
1752
+ }
1753
+ ),
1754
+ ListFooterComponent: sessions.length > 1 ? /* @__PURE__ */ jsx(View, { style: { marginTop: 16 }, children: /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: handleRevokeAll, label: L.REVOKE_ALL_OTHER_SESSIONS }) }) }) : null
1755
+ }
1756
+ ),
1757
+ /* @__PURE__ */ jsx(View, { style: { alignItems: "center", marginTop: 8 }, children: /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => router.back(), label: L.GO_BACK }) }) })
1758
+ ] }) });
1759
+ }
1760
+ function TwoFactorSettingsScreen({ onSuccess }) {
1761
+ const { localization: L } = useAuthLocalization();
1762
+ const router = useRouter();
1763
+ const { data: user } = useUser();
1764
+ const [state, setState] = useState({ step: "idle" });
1765
+ const [totpCode, setTotpCode] = useState("");
1766
+ const [password, setPassword] = useState("");
1767
+ const [copied, setCopied] = useState(false);
1768
+ const setupMutation = useMutation({
1769
+ mutationFn: async (_pw) => {
1770
+ throw new NotImplementedError({ feature: "Two-factor setup" });
1771
+ }
1772
+ });
1773
+ const verifyMutation = useMutation({
1774
+ mutationFn: async (_code) => {
1775
+ throw new NotImplementedError({ feature: "Two-factor verification" });
1776
+ }
1777
+ });
1778
+ const disableMutation = useMutation({
1779
+ mutationFn: async (_pw) => {
1780
+ throw new NotImplementedError({ feature: "Two-factor disable" });
1781
+ }
1782
+ });
1783
+ const handleEnable = useCallback(() => {
1784
+ if (!password) return;
1785
+ setupMutation.mutate(password, {
1786
+ onSuccess: (data) => {
1787
+ setPassword("");
1788
+ setState({
1789
+ step: "setup",
1790
+ totpURI: data?.totpURI ?? "",
1791
+ secret: data?.secret ?? ""
1792
+ });
1793
+ }
1794
+ });
1795
+ }, [password, setupMutation]);
1796
+ const handleVerify = useCallback(() => {
1797
+ if (totpCode.length !== 6) return;
1798
+ verifyMutation.mutate(totpCode, {
1799
+ onSuccess: (data) => {
1800
+ const codes = data?.backupCodes ?? [];
1801
+ setState({ step: "backup", backupCodes: codes });
1802
+ onSuccess?.();
1803
+ }
1804
+ });
1805
+ }, [totpCode, verifyMutation, onSuccess]);
1806
+ const handleCopyBackupCodes = useCallback(async () => {
1807
+ if (state.step !== "backup") return;
1808
+ try {
1809
+ await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
1810
+ setCopied(true);
1811
+ setTimeout(() => setCopied(false), 2e3);
1812
+ } catch {
1813
+ }
1814
+ }, [state]);
1815
+ const handleDisable = useCallback(() => {
1816
+ Alert.alert(L.DISABLE_TWO_FACTOR, L.CONFIRM_DISABLE_2FA, [
1817
+ { text: L.CANCEL, style: "cancel" },
1818
+ {
1819
+ text: L.DISABLE,
1820
+ style: "destructive",
1821
+ onPress: () => setState({ step: "disabling" })
1822
+ }
1823
+ ]);
1824
+ }, [L]);
1825
+ const handleConfirmDisable = useCallback(() => {
1826
+ if (!password) return;
1827
+ disableMutation.mutate(password, {
1828
+ onSuccess: () => {
1829
+ setState({ step: "idle" });
1830
+ setPassword("");
1831
+ onSuccess?.();
1832
+ }
1833
+ });
1834
+ }, [password, disableMutation, onSuccess]);
1835
+ const isEnabled = user?.twoFactorEnabled === true;
1836
+ const isPending = setupMutation.isPending || verifyMutation.isPending || disableMutation.isPending;
1837
+ const error = setupMutation.error ?? verifyMutation.error ?? disableMutation.error;
1838
+ return /* @__PURE__ */ jsx(AuthContainer, { children: /* @__PURE__ */ jsxs(ScrollView, { contentContainerStyle: { gap: 24 }, children: [
1839
+ /* @__PURE__ */ jsxs(View, { style: { gap: 4, alignItems: "center" }, children: [
1840
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 24, fontWeight: "700", color: "#1f2937" }, children: L.TWO_FACTOR_PROMPT }),
1841
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#6b7280" }, children: L.TWO_FACTOR_DESCRIPTION })
1842
+ ] }),
1843
+ state.step === "idle" && !isEnabled && /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
1844
+ /* @__PURE__ */ jsxs(Text, { style: { textAlign: "center", color: "#6b7280" }, children: [
1845
+ L.STATUS,
1846
+ ": ",
1847
+ L.DISABLED
1848
+ ] }),
1849
+ /* @__PURE__ */ jsx(Host, { matchContents: true, style: { opacity: isPending ? 0.5 : 1 }, children: /* @__PURE__ */ jsx(
1850
+ Button,
1851
+ {
1852
+ onPress: () => {
1853
+ if (isPending) return;
1854
+ setState({ step: "confirm-password" });
1855
+ },
1856
+ label: L.ENABLE_TWO_FACTOR
1857
+ }
1858
+ ) })
1859
+ ] }),
1860
+ state.step === "confirm-password" && /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
1861
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#374151", textAlign: "center" }, children: L.PASSWORD }),
1862
+ /* @__PURE__ */ jsx(
1863
+ TextInput,
1864
+ {
1865
+ value: password,
1866
+ onChangeText: setPassword,
1867
+ placeholder: L.PASSWORD,
1868
+ secureTextEntry: true,
1869
+ editable: !setupMutation.isPending,
1870
+ style: {
1871
+ borderWidth: 1,
1872
+ borderColor: "#d1d5db",
1873
+ borderRadius: 8,
1874
+ padding: 12,
1875
+ fontSize: 16
1876
+ }
1877
+ }
1878
+ ),
1879
+ setupMutation.isPending && /* @__PURE__ */ jsx(ActivityIndicator, { size: "small" }),
1880
+ /* @__PURE__ */ jsx(Host, { matchContents: true, style: { opacity: setupMutation.isPending ? 0.5 : 1 }, children: /* @__PURE__ */ jsx(
1881
+ Button,
1882
+ {
1883
+ onPress: () => {
1884
+ if (setupMutation.isPending) return;
1885
+ handleEnable();
1886
+ },
1887
+ label: setupMutation.isPending ? L.LOADING : L.ENABLE_TWO_FACTOR
1888
+ }
1889
+ ) }),
1890
+ /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(
1891
+ Button,
1892
+ {
1893
+ onPress: () => {
1894
+ setState({ step: "idle" });
1895
+ setPassword("");
1896
+ },
1897
+ label: L.CANCEL
1898
+ }
1899
+ ) })
1900
+ ] }),
1901
+ state.step === "idle" && isEnabled && /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
1902
+ /* @__PURE__ */ jsx(SuccessFeedback, { message: `${L.STATUS}: ${L.ENABLED}` }),
1903
+ /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: handleDisable, label: L.DISABLE_TWO_FACTOR }) })
1904
+ ] }),
1905
+ state.step === "setup" && /* @__PURE__ */ jsxs(View, { style: { gap: 16, alignItems: "center" }, children: [
1906
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#374151", textAlign: "center" }, children: L.SCAN_QR_CODE }),
1907
+ /* @__PURE__ */ jsx(
1908
+ View,
1909
+ {
1910
+ style: {
1911
+ width: 200,
1912
+ height: 200,
1913
+ backgroundColor: "#f3f4f6",
1914
+ justifyContent: "center",
1915
+ alignItems: "center",
1916
+ borderRadius: 8
1917
+ },
1918
+ children: /* @__PURE__ */ jsx(Text, { style: { fontSize: 12, color: "#9ca3af" }, children: L.SCAN_QR_CODE })
1919
+ }
1920
+ ),
1921
+ /* @__PURE__ */ jsxs(View, { style: { gap: 4, alignItems: "center" }, children: [
1922
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 12, color: "#6b7280" }, children: L.MANUAL_ENTRY_CODE }),
1923
+ /* @__PURE__ */ jsx(
1924
+ Text,
1925
+ {
1926
+ style: {
1927
+ fontFamily: "monospace",
1928
+ fontSize: 16,
1929
+ fontWeight: "600",
1930
+ color: "#1f2937",
1931
+ letterSpacing: 2
1932
+ },
1933
+ children: state.secret
1934
+ }
1935
+ )
1936
+ ] }),
1937
+ /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(
1938
+ Button,
1939
+ {
1940
+ onPress: () => setState({
1941
+ step: "verify",
1942
+ totpURI: state.totpURI,
1943
+ secret: state.secret
1944
+ }),
1945
+ label: L.NEXT
1946
+ }
1947
+ ) })
1948
+ ] }),
1949
+ state.step === "verify" && /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
1950
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#374151", textAlign: "center" }, children: L.ENTER_CODE_TO_VERIFY }),
1951
+ /* @__PURE__ */ jsx(
1952
+ TextInput,
1953
+ {
1954
+ value: totpCode,
1955
+ onChangeText: setTotpCode,
1956
+ placeholder: L.ENTER_TOTP_CODE,
1957
+ keyboardType: "number-pad",
1958
+ maxLength: 6,
1959
+ editable: !verifyMutation.isPending,
1960
+ style: {
1961
+ borderWidth: 1,
1962
+ borderColor: "#d1d5db",
1963
+ borderRadius: 8,
1964
+ padding: 12,
1965
+ fontSize: 24,
1966
+ textAlign: "center",
1967
+ letterSpacing: 8,
1968
+ fontFamily: "monospace"
1969
+ }
1970
+ }
1971
+ ),
1972
+ verifyMutation.isPending && /* @__PURE__ */ jsx(ActivityIndicator, { size: "small" }),
1973
+ /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: handleVerify, label: L.TWO_FACTOR_ACTION }) })
1974
+ ] }),
1975
+ state.step === "backup" && /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
1976
+ /* @__PURE__ */ jsx(SuccessFeedback, { message: L.TWO_FACTOR_SETUP_SUCCESS }),
1977
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#374151", textAlign: "center" }, children: L.BACKUP_CODES_DESCRIPTION }),
1978
+ /* @__PURE__ */ jsx(
1979
+ View,
1980
+ {
1981
+ style: {
1982
+ backgroundColor: "#f9fafb",
1983
+ borderRadius: 8,
1984
+ padding: 16,
1985
+ gap: 8
1986
+ },
1987
+ children: state.backupCodes.map((code) => /* @__PURE__ */ jsx(
1988
+ Text,
1989
+ {
1990
+ style: {
1991
+ fontFamily: "monospace",
1992
+ fontSize: 16,
1993
+ textAlign: "center",
1994
+ color: "#1f2937"
1995
+ },
1996
+ children: code
1997
+ },
1998
+ code
1999
+ ))
2000
+ }
2001
+ ),
2002
+ /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(
2003
+ Button,
2004
+ {
2005
+ onPress: handleCopyBackupCodes,
2006
+ label: copied ? L.BACKUP_CODES_COPIED : L.COPY_BACKUP_CODES
2007
+ }
2008
+ ) }),
2009
+ /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => setState({ step: "idle" }), label: L.FINISH }) })
2010
+ ] }),
2011
+ state.step === "disabling" && /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
2012
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#374151", textAlign: "center" }, children: L.CONFIRM_DISABLE_2FA }),
2013
+ /* @__PURE__ */ jsx(
2014
+ TextInput,
2015
+ {
2016
+ value: password,
2017
+ onChangeText: setPassword,
2018
+ placeholder: L.PASSWORD,
2019
+ secureTextEntry: true,
2020
+ editable: !disableMutation.isPending,
2021
+ style: {
2022
+ borderWidth: 1,
2023
+ borderColor: "#d1d5db",
2024
+ borderRadius: 8,
2025
+ padding: 12,
2026
+ fontSize: 16
2027
+ }
2028
+ }
2029
+ ),
2030
+ disableMutation.isPending && /* @__PURE__ */ jsx(ActivityIndicator, { size: "small" }),
2031
+ /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: handleConfirmDisable, label: L.DISABLE_TWO_FACTOR }) }),
2032
+ /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => setState({ step: "idle" }), label: L.CANCEL }) })
2033
+ ] }),
2034
+ error && /* @__PURE__ */ jsx(ErrorFeedback, { message: error.message }),
2035
+ /* @__PURE__ */ jsx(View, { style: { alignItems: "center", marginTop: 8 }, children: /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => router.back(), label: L.GO_BACK }) }) })
2036
+ ] }) });
2037
+ }
2038
+ var AVATAR_SIZE = 120;
2039
+ function getInitials(name) {
2040
+ if (!name) return "?";
2041
+ const parts = name.trim().split(/\s+/);
2042
+ if (parts.length >= 2) {
2043
+ return `${parts[0][0]}${parts[1][0]}`.toUpperCase();
2044
+ }
2045
+ return name.slice(0, 2).toUpperCase();
2046
+ }
2047
+ function UpdateAvatarScreen({ onSuccess }) {
2048
+ const { localization: L } = useAuthLocalization();
2049
+ const router = useRouter();
2050
+ const client = useAuthClient();
2051
+ const queryClient = useQueryClient();
2052
+ const { data: user } = useUser();
2053
+ const [selectedUri, setSelectedUri] = useState(null);
2054
+ const [successMessage, setSuccessMessage] = useState(null);
2055
+ const uploadMutation = useMutation({
2056
+ mutationFn: async (imageUri) => {
2057
+ const response = await client.updateUser({ image: imageUri });
2058
+ if (response.error) {
2059
+ throw new RequestFailedError({ message: response.error.message ?? L.REQUEST_FAILED });
2060
+ }
2061
+ return response.data;
2062
+ }
2063
+ });
2064
+ const deleteMutation = useMutation({
2065
+ mutationFn: async () => {
2066
+ const response = await client.updateUser({ image: "" });
2067
+ if (response.error) {
2068
+ throw new RequestFailedError({ message: response.error.message ?? L.REQUEST_FAILED });
2069
+ }
2070
+ return response.data;
2071
+ }
2072
+ });
2073
+ const handlePickImage = useCallback(async () => {
2074
+ const result = await ImagePicker.launchImageLibraryAsync({
2075
+ mediaTypes: "images",
2076
+ allowsEditing: true,
2077
+ aspect: [1, 1],
2078
+ quality: 0.8
2079
+ });
2080
+ if (!result.canceled && result.assets[0]) {
2081
+ setSelectedUri(result.assets[0].uri);
2082
+ }
2083
+ }, []);
2084
+ const handleDelete = useCallback(() => {
2085
+ deleteMutation.mutate(void 0, {
2086
+ onSuccess: () => {
2087
+ queryClient.invalidateQueries({ queryKey: authKeys.user() });
2088
+ setSuccessMessage(L.AVATAR_DELETED);
2089
+ setTimeout(() => setSuccessMessage(null), 3e3);
2090
+ onSuccess?.();
2091
+ }
2092
+ });
2093
+ }, [deleteMutation, queryClient, L, onSuccess]);
2094
+ const showActionSheet = useCallback(() => {
2095
+ const handleAction = (buttonIndex) => {
2096
+ if (buttonIndex === 1) {
2097
+ handlePickImage();
2098
+ } else if (buttonIndex === 2) {
2099
+ handleDelete();
2100
+ }
2101
+ };
2102
+ if (Platform.OS === "ios") {
2103
+ ActionSheetIOS.showActionSheetWithOptions(
2104
+ {
2105
+ options: [L.CANCEL, L.CLICK_TO_UPLOAD, L.DELETE_AVATAR],
2106
+ cancelButtonIndex: 0,
2107
+ destructiveButtonIndex: 2
2108
+ },
2109
+ handleAction
2110
+ );
2111
+ } else {
2112
+ Alert.alert(L.AVATAR, void 0, [
2113
+ { text: L.CLICK_TO_UPLOAD, onPress: () => handleAction(1) },
2114
+ { text: L.DELETE_AVATAR, style: "destructive", onPress: () => handleAction(2) },
2115
+ { text: L.CANCEL, style: "cancel" }
2116
+ ]);
2117
+ }
2118
+ }, [L, handlePickImage, handleDelete]);
2119
+ const handleUpload = useCallback(() => {
2120
+ if (!selectedUri) return;
2121
+ uploadMutation.mutate(selectedUri, {
2122
+ onSuccess: () => {
2123
+ queryClient.invalidateQueries({ queryKey: authKeys.user() });
2124
+ setSelectedUri(null);
2125
+ setSuccessMessage(L.AVATAR_UPDATED);
2126
+ setTimeout(() => setSuccessMessage(null), 3e3);
2127
+ onSuccess?.();
2128
+ }
2129
+ });
2130
+ }, [selectedUri, uploadMutation, queryClient, L, onSuccess]);
2131
+ const displayUri = selectedUri ?? user?.image;
2132
+ const isPending = uploadMutation.isPending || deleteMutation.isPending;
2133
+ const error = uploadMutation.error ?? deleteMutation.error;
2134
+ return /* @__PURE__ */ jsx(AuthContainer, { children: /* @__PURE__ */ jsxs(View, { style: { gap: 24 }, children: [
2135
+ /* @__PURE__ */ jsxs(View, { style: { gap: 4, alignItems: "center" }, children: [
2136
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 24, fontWeight: "700", color: "#1f2937" }, children: L.AVATAR }),
2137
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#6b7280" }, children: L.AVATAR_DESCRIPTION })
2138
+ ] }),
2139
+ successMessage ? /* @__PURE__ */ jsx(SuccessFeedback, { message: successMessage }) : /* @__PURE__ */ jsxs(View, { style: { gap: 16, alignItems: "center" }, children: [
2140
+ /* @__PURE__ */ jsx(Pressable, { onPress: showActionSheet, disabled: isPending, children: displayUri ? /* @__PURE__ */ jsx(
2141
+ Image,
2142
+ {
2143
+ source: { uri: displayUri },
2144
+ style: {
2145
+ width: AVATAR_SIZE,
2146
+ height: AVATAR_SIZE,
2147
+ borderRadius: AVATAR_SIZE / 2
2148
+ }
2149
+ }
2150
+ ) : /* @__PURE__ */ jsx(
2151
+ View,
2152
+ {
2153
+ style: {
2154
+ width: AVATAR_SIZE,
2155
+ height: AVATAR_SIZE,
2156
+ borderRadius: AVATAR_SIZE / 2,
2157
+ backgroundColor: "#e5e7eb",
2158
+ justifyContent: "center",
2159
+ alignItems: "center"
2160
+ },
2161
+ children: /* @__PURE__ */ jsx(Text, { style: { fontSize: 36, fontWeight: "700", color: "#6b7280" }, children: getInitials(user?.name) })
2162
+ }
2163
+ ) }),
2164
+ isPending && /* @__PURE__ */ jsx(ActivityIndicator, { size: "small" }),
2165
+ selectedUri && /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: handleUpload, label: L.UPLOAD_AVATAR }) }),
2166
+ error && /* @__PURE__ */ jsx(ErrorFeedback, { message: error.message }),
2167
+ /* @__PURE__ */ jsx(View, { style: { alignItems: "center", marginTop: 8 }, children: /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => router.back(), label: L.GO_BACK }) }) })
2168
+ ] })
2169
+ ] }) });
2170
+ }
2171
+ function UpdateNameScreen({ onSuccess }) {
2172
+ const { localization: L } = useAuthLocalization();
2173
+ const router = useRouter();
2174
+ const client = useAuthClient();
2175
+ const queryClient = useQueryClient();
2176
+ const { data: user } = useUser();
2177
+ const [showSuccess, setShowSuccess] = useState(false);
2178
+ const form = useForm({
2179
+ resolver: zodResolver(updateNameSchema),
2180
+ defaultValues: { name: user?.name ?? "" },
2181
+ mode: "onChange"
2182
+ });
2183
+ const updateMutation = useMutation({
2184
+ mutationFn: async (data) => {
2185
+ const response = await client.updateUser({ name: data.name });
2186
+ if (response.error) {
2187
+ throw new RequestFailedError({ message: response.error.message ?? L.REQUEST_FAILED });
2188
+ }
2189
+ return response.data;
2190
+ }
2191
+ });
2192
+ const handleSubmit = useCallback(
2193
+ (data) => {
2194
+ updateMutation.mutate(data, {
2195
+ onSuccess: () => {
2196
+ queryClient.invalidateQueries({ queryKey: authKeys.user() });
2197
+ setShowSuccess(true);
2198
+ setTimeout(() => setShowSuccess(false), 3e3);
2199
+ onSuccess?.();
2200
+ }
2201
+ });
2202
+ },
2203
+ [updateMutation, queryClient, onSuccess]
2204
+ );
2205
+ return /* @__PURE__ */ jsx(AuthContainer, { children: /* @__PURE__ */ jsxs(View, { style: { gap: 24 }, children: [
2206
+ /* @__PURE__ */ jsxs(View, { style: { gap: 4, alignItems: "center" }, children: [
2207
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 24, fontWeight: "700", color: "#1f2937" }, children: L.UPDATE_NAME }),
2208
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#6b7280" }, children: L.UPDATE_NAME_DESCRIPTION })
2209
+ ] }),
2210
+ showSuccess ? /* @__PURE__ */ jsx(SuccessFeedback, { message: L.NAME_UPDATED }) : /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
2211
+ /* @__PURE__ */ jsx(
2212
+ Controller,
2213
+ {
2214
+ control: form.control,
2215
+ name: "name",
2216
+ render: ({ field: { onChange, onBlur, value }, fieldState: { error } }) => /* @__PURE__ */ jsx(
2217
+ FormInput,
2218
+ {
2219
+ label: L.NAME,
2220
+ placeholder: L.NAME_PLACEHOLDER,
2221
+ editable: !updateMutation.isPending,
2222
+ onChangeText: onChange,
2223
+ onBlur,
2224
+ value,
2225
+ error: error?.message
2226
+ }
2227
+ )
2228
+ }
2229
+ ),
2230
+ updateMutation.error && /* @__PURE__ */ jsx(ErrorFeedback, { message: updateMutation.error.message }),
2231
+ /* @__PURE__ */ jsx(
2232
+ SubmitButton,
2233
+ {
2234
+ label: L.SAVE,
2235
+ loading: updateMutation.isPending,
2236
+ loadingLabel: L.LOADING,
2237
+ onPress: form.handleSubmit(handleSubmit)
2238
+ }
2239
+ ),
2240
+ /* @__PURE__ */ jsx(View, { style: { alignItems: "center", marginTop: 8 }, children: /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => router.back(), label: L.GO_BACK }) }) })
2241
+ ] })
2242
+ ] }) });
2243
+ }
2244
+ function UpdateUsernameScreen({ onSuccess }) {
2245
+ const { localization: L } = useAuthLocalization();
2246
+ const router = useRouter();
2247
+ useAuthClient();
2248
+ const queryClient = useQueryClient();
2249
+ const { data: _user } = useUser();
2250
+ const [showSuccess, setShowSuccess] = useState(false);
2251
+ const form = useForm({
2252
+ resolver: zodResolver(updateUsernameSchema),
2253
+ // BA 1.5: username not available on user object
2254
+ defaultValues: { username: "" },
2255
+ mode: "onChange"
2256
+ });
2257
+ const updateMutation = useMutation({
2258
+ mutationFn: async (_data) => {
2259
+ throw new NotImplementedError({ feature: "Username updates" });
2260
+ }
2261
+ });
2262
+ const handleSubmit = useCallback(
2263
+ (data) => {
2264
+ updateMutation.mutate(data, {
2265
+ onSuccess: () => {
2266
+ queryClient.invalidateQueries({ queryKey: authKeys.user() });
2267
+ setShowSuccess(true);
2268
+ setTimeout(() => setShowSuccess(false), 3e3);
2269
+ onSuccess?.();
2270
+ }
2271
+ });
2272
+ },
2273
+ [updateMutation, queryClient, onSuccess]
2274
+ );
2275
+ return /* @__PURE__ */ jsx(AuthContainer, { children: /* @__PURE__ */ jsxs(View, { style: { gap: 24 }, children: [
2276
+ /* @__PURE__ */ jsxs(View, { style: { gap: 4, alignItems: "center" }, children: [
2277
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 24, fontWeight: "700", color: "#1f2937" }, children: L.CHANGE_USERNAME }),
2278
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#6b7280" }, children: L.CHANGE_USERNAME_DESCRIPTION })
2279
+ ] }),
2280
+ showSuccess ? /* @__PURE__ */ jsx(SuccessFeedback, { message: L.USERNAME_UPDATED }) : /* @__PURE__ */ jsxs(View, { style: { gap: 16 }, children: [
2281
+ /* @__PURE__ */ jsx(
2282
+ Controller,
2283
+ {
2284
+ control: form.control,
2285
+ name: "username",
2286
+ render: ({ field: { onChange, onBlur, value }, fieldState: { error } }) => /* @__PURE__ */ jsx(
2287
+ FormInput,
2288
+ {
2289
+ label: L.USERNAME,
2290
+ placeholder: L.USERNAME_PLACEHOLDER,
2291
+ autoCapitalize: "none",
2292
+ editable: !updateMutation.isPending,
2293
+ onChangeText: onChange,
2294
+ onBlur,
2295
+ value,
2296
+ error: error?.message
2297
+ }
2298
+ )
2299
+ }
2300
+ ),
2301
+ updateMutation.error && /* @__PURE__ */ jsx(ErrorFeedback, { message: updateMutation.error.message }),
2302
+ /* @__PURE__ */ jsx(
2303
+ SubmitButton,
2304
+ {
2305
+ label: L.SAVE,
2306
+ loading: updateMutation.isPending,
2307
+ loadingLabel: L.LOADING,
2308
+ onPress: form.handleSubmit(handleSubmit)
2309
+ }
2310
+ ),
2311
+ /* @__PURE__ */ jsx(View, { style: { alignItems: "center", marginTop: 8 }, children: /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => router.back(), label: L.GO_BACK }) }) })
2312
+ ] })
2313
+ ] }) });
2314
+ }
2315
+ function OrganizationMembersScreen({
2316
+ organizationId,
2317
+ isAdmin = false,
2318
+ onSuccess
2319
+ }) {
2320
+ const { localization: L } = useAuthLocalization();
2321
+ const router = useRouter();
2322
+ useAuthClient();
2323
+ const queryClient = useQueryClient();
2324
+ const [searchQuery, setSearchQuery] = useState("");
2325
+ const membersQuery = useQuery({
2326
+ queryKey: ["organizations", organizationId, "members"],
2327
+ queryFn: async () => {
2328
+ throw new NotImplementedError({ feature: "Organization member management" });
2329
+ }
2330
+ });
2331
+ const updateRoleMutation = useMutation({
2332
+ mutationFn: async (_memberId_role) => {
2333
+ throw new NotImplementedError({ feature: "Organization role updates" });
2334
+ },
2335
+ onSuccess: () => {
2336
+ queryClient.invalidateQueries({
2337
+ queryKey: ["organizations", organizationId, "members"]
2338
+ });
2339
+ }
2340
+ });
2341
+ const removeMutation = useMutation({
2342
+ mutationFn: async (_memberId) => {
2343
+ throw new NotImplementedError({ feature: "Organization member removal" });
2344
+ },
2345
+ onSuccess: () => {
2346
+ queryClient.invalidateQueries({
2347
+ queryKey: ["organizations", organizationId, "members"]
2348
+ });
2349
+ onSuccess?.();
2350
+ }
2351
+ });
2352
+ const showMemberActionSheet = useCallback(
2353
+ (callback) => {
2354
+ const options = [L.ADMIN, L.MEMBER, L.REMOVE_MEMBER, L.CANCEL];
2355
+ if (Platform.OS === "ios") {
2356
+ ActionSheetIOS.showActionSheetWithOptions(
2357
+ {
2358
+ options,
2359
+ cancelButtonIndex: 3,
2360
+ destructiveButtonIndex: 2
2361
+ },
2362
+ callback
2363
+ );
2364
+ } else {
2365
+ Alert.alert(L.MANAGE_ROLE, void 0, [
2366
+ { text: L.ADMIN, onPress: () => callback(0) },
2367
+ { text: L.MEMBER, onPress: () => callback(1) },
2368
+ { text: L.REMOVE_MEMBER, style: "destructive", onPress: () => callback(2) },
2369
+ { text: L.CANCEL, style: "cancel" }
2370
+ ]);
2371
+ }
2372
+ },
2373
+ [L]
2374
+ );
2375
+ const handleMemberAction = useCallback(
2376
+ (member) => {
2377
+ if (!isAdmin) return;
2378
+ showMemberActionSheet((buttonIndex) => {
2379
+ const roleMap = { 0: "admin", 1: "member" };
2380
+ if (buttonIndex === 2) {
2381
+ Alert.alert(L.REMOVE_MEMBER, L.CONFIRM_REMOVE_MEMBER, [
2382
+ { text: L.CANCEL, style: "cancel" },
2383
+ {
2384
+ text: L.REMOVE,
2385
+ style: "destructive",
2386
+ onPress: () => removeMutation.mutate(member.id)
2387
+ }
2388
+ ]);
2389
+ } else if (roleMap[buttonIndex]) {
2390
+ updateRoleMutation.mutate({
2391
+ memberId: member.id,
2392
+ role: roleMap[buttonIndex]
2393
+ });
2394
+ }
2395
+ });
2396
+ },
2397
+ [isAdmin, L, removeMutation, updateRoleMutation, showMemberActionSheet]
2398
+ );
2399
+ const allMembers = membersQuery.data ?? [];
2400
+ const filteredMembers = searchQuery ? allMembers.filter(
2401
+ (m) => (m.user?.name ?? "").toLowerCase().includes(searchQuery.toLowerCase()) || (m.user?.email ?? "").toLowerCase().includes(searchQuery.toLowerCase())
2402
+ ) : allMembers;
2403
+ updateRoleMutation.isPending || removeMutation.isPending;
2404
+ const error = updateRoleMutation.error ?? removeMutation.error;
2405
+ return /* @__PURE__ */ jsx(AuthContainer, { children: /* @__PURE__ */ jsxs(View, { style: { gap: 24, flex: 1 }, children: [
2406
+ /* @__PURE__ */ jsxs(View, { style: { gap: 4, alignItems: "center" }, children: [
2407
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 24, fontWeight: "700", color: "#1f2937" }, children: L.MEMBERS }),
2408
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#6b7280" }, children: L.MEMBERS_SUBTITLE })
2409
+ ] }),
2410
+ allMembers.length > 10 && /* @__PURE__ */ jsx(
2411
+ TextInput,
2412
+ {
2413
+ value: searchQuery,
2414
+ onChangeText: setSearchQuery,
2415
+ placeholder: L.MEMBER_EMAIL_PLACEHOLDER,
2416
+ autoCapitalize: "none",
2417
+ style: {
2418
+ borderWidth: 1,
2419
+ borderColor: "#d1d5db",
2420
+ borderRadius: 8,
2421
+ padding: 12,
2422
+ fontSize: 16
2423
+ }
2424
+ }
2425
+ ),
2426
+ membersQuery.isLoading && /* @__PURE__ */ jsx(ActivityIndicator, { size: "large" }),
2427
+ membersQuery.error && /* @__PURE__ */ jsx(ErrorFeedback, { message: membersQuery.error.message }),
2428
+ !membersQuery.isLoading && filteredMembers.length === 0 && /* @__PURE__ */ jsx(Text, { style: { textAlign: "center", color: "#6b7280" }, children: L.NO_MEMBERS }),
2429
+ filteredMembers.length > 0 && /* @__PURE__ */ jsx(
2430
+ FlatList,
2431
+ {
2432
+ data: filteredMembers,
2433
+ keyExtractor: (item) => item.id,
2434
+ refreshControl: /* @__PURE__ */ jsx(
2435
+ RefreshControl,
2436
+ {
2437
+ refreshing: membersQuery.isRefetching,
2438
+ onRefresh: () => membersQuery.refetch()
2439
+ }
2440
+ ),
2441
+ renderItem: ({ item }) => {
2442
+ const isOwner = item.role === "owner";
2443
+ return /* @__PURE__ */ jsxs(
2444
+ View,
2445
+ {
2446
+ style: {
2447
+ flexDirection: "row",
2448
+ justifyContent: "space-between",
2449
+ alignItems: "center",
2450
+ paddingVertical: 12,
2451
+ borderBottomWidth: 1,
2452
+ borderBottomColor: "#f3f4f6"
2453
+ },
2454
+ children: [
2455
+ /* @__PURE__ */ jsxs(View, { style: { flex: 1, gap: 2 }, children: [
2456
+ /* @__PURE__ */ jsxs(View, { style: { flexDirection: "row", alignItems: "center", gap: 8 }, children: [
2457
+ /* @__PURE__ */ jsx(
2458
+ Text,
2459
+ {
2460
+ style: {
2461
+ fontSize: 16,
2462
+ fontWeight: isOwner ? "700" : "500",
2463
+ color: "#1f2937"
2464
+ },
2465
+ children: item.user?.name ?? L.UNKNOWN
2466
+ }
2467
+ ),
2468
+ isOwner && /* @__PURE__ */ jsx(Text, { style: { fontSize: 11, fontWeight: "600", color: "#f59e0b" }, children: L.OWNER })
2469
+ ] }),
2470
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 12, color: "#9ca3af" }, children: item.user?.email ?? "" }),
2471
+ /* @__PURE__ */ jsxs(Text, { style: { fontSize: 12, color: "#9ca3af" }, children: [
2472
+ L.ROLE,
2473
+ ": ",
2474
+ item.role
2475
+ ] })
2476
+ ] }),
2477
+ isAdmin && !isOwner && /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => handleMemberAction(item), label: L.MANAGE_ROLE }) })
2478
+ ]
2479
+ }
2480
+ );
2481
+ }
2482
+ }
2483
+ ),
2484
+ error && /* @__PURE__ */ jsx(ErrorFeedback, { message: error.message }),
2485
+ /* @__PURE__ */ jsx(View, { style: { alignItems: "center", marginTop: 8 }, children: /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => router.back(), label: L.GO_BACK }) }) })
2486
+ ] }) });
2487
+ }
2488
+ function OrganizationSwitcherScreen({
2489
+ currentOrgId,
2490
+ onSwitch
2491
+ }) {
2492
+ const { localization: L } = useAuthLocalization();
2493
+ const router = useRouter();
2494
+ useAuthClient();
2495
+ const [searchQuery, setSearchQuery] = useState("");
2496
+ const orgsQuery = useQuery({
2497
+ queryKey: ["organizations", "list"],
2498
+ queryFn: async () => {
2499
+ throw new NotImplementedError({ feature: "Organization listing" });
2500
+ }
2501
+ });
2502
+ const setActiveMutation = useMutation({
2503
+ mutationFn: async (_orgId) => {
2504
+ throw new NotImplementedError({ feature: "Organization switching" });
2505
+ }
2506
+ });
2507
+ const handleSelect = useCallback(
2508
+ (orgId) => {
2509
+ setActiveMutation.mutate(orgId, {
2510
+ onSuccess: () => {
2511
+ onSwitch?.(orgId);
2512
+ router.back();
2513
+ }
2514
+ });
2515
+ },
2516
+ [setActiveMutation, onSwitch, router]
2517
+ );
2518
+ const allOrgs = orgsQuery.data ?? [];
2519
+ const filteredOrgs = searchQuery ? allOrgs.filter((org) => (org.name ?? "").toLowerCase().includes(searchQuery.toLowerCase())) : allOrgs;
2520
+ return /* @__PURE__ */ jsx(AuthContainer, { children: /* @__PURE__ */ jsxs(View, { style: { gap: 24, flex: 1 }, children: [
2521
+ /* @__PURE__ */ jsxs(View, { style: { gap: 4, alignItems: "center" }, children: [
2522
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 24, fontWeight: "700", color: "#1f2937" }, children: L.ORGANIZATIONS }),
2523
+ /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#6b7280" }, children: L.SELECT_ORGANIZATION })
2524
+ ] }),
2525
+ allOrgs.length > 5 && /* @__PURE__ */ jsx(
2526
+ TextInput,
2527
+ {
2528
+ value: searchQuery,
2529
+ onChangeText: setSearchQuery,
2530
+ placeholder: L.SEARCH_ORGANIZATIONS,
2531
+ autoCapitalize: "none",
2532
+ style: {
2533
+ borderWidth: 1,
2534
+ borderColor: "#d1d5db",
2535
+ borderRadius: 8,
2536
+ padding: 12,
2537
+ fontSize: 16
2538
+ }
2539
+ }
2540
+ ),
2541
+ orgsQuery.isLoading && /* @__PURE__ */ jsx(ActivityIndicator, { size: "large" }),
2542
+ orgsQuery.error && /* @__PURE__ */ jsx(ErrorFeedback, { message: orgsQuery.error.message }),
2543
+ !orgsQuery.isLoading && filteredOrgs.length === 0 && /* @__PURE__ */ jsx(Text, { style: { textAlign: "center", color: "#6b7280" }, children: L.NO_ORGANIZATIONS }),
2544
+ filteredOrgs.length > 0 && /* @__PURE__ */ jsx(
2545
+ FlatList,
2546
+ {
2547
+ data: filteredOrgs,
2548
+ keyExtractor: (item) => item.id,
2549
+ refreshControl: /* @__PURE__ */ jsx(
2550
+ RefreshControl,
2551
+ {
2552
+ refreshing: orgsQuery.isRefetching,
2553
+ onRefresh: () => orgsQuery.refetch()
2554
+ }
2555
+ ),
2556
+ renderItem: ({ item }) => {
2557
+ const isCurrent = item.id === currentOrgId;
2558
+ return /* @__PURE__ */ jsxs(
2559
+ View,
2560
+ {
2561
+ style: {
2562
+ flexDirection: "row",
2563
+ justifyContent: "space-between",
2564
+ alignItems: "center",
2565
+ paddingVertical: 12,
2566
+ borderBottomWidth: 1,
2567
+ borderBottomColor: "#f3f4f6"
2568
+ },
2569
+ children: [
2570
+ /* @__PURE__ */ jsxs(View, { style: { flex: 1, gap: 2 }, children: [
2571
+ /* @__PURE__ */ jsx(
2572
+ Text,
2573
+ {
2574
+ style: {
2575
+ fontSize: 16,
2576
+ fontWeight: isCurrent ? "700" : "500",
2577
+ color: "#1f2937"
2578
+ },
2579
+ children: item.name
2580
+ }
2581
+ ),
2582
+ item.members && /* @__PURE__ */ jsx(Text, { style: { fontSize: 12, color: "#9ca3af" }, children: L.MEMBER_COUNT.replace("{{count}}", String(item.members.length ?? 0)) })
2583
+ ] }),
2584
+ isCurrent ? /* @__PURE__ */ jsx(Text, { style: { fontSize: 14, color: "#16a34a", fontWeight: "600" }, children: "\u2713" }) : /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => handleSelect(item.id), label: L.SELECT_ORGANIZATION }) })
2585
+ ]
2586
+ }
2587
+ );
2588
+ }
2589
+ }
2590
+ ),
2591
+ /* @__PURE__ */ jsx(View, { style: { alignItems: "center", marginTop: 8 }, children: /* @__PURE__ */ jsx(Host, { matchContents: true, children: /* @__PURE__ */ jsx(Button, { onPress: () => router.back(), label: L.GO_BACK }) }) })
2592
+ ] }) });
2593
+ }
2594
+ function useAuthRedirect() {
2595
+ const router = useRouter();
2596
+ const redirectToSignIn = useCallback(
2597
+ (returnUrl) => {
2598
+ if (returnUrl) {
2599
+ router.replace(`/sign-in?returnUrl=${encodeURIComponent(returnUrl)}`);
2600
+ } else {
2601
+ router.replace("/sign-in");
2602
+ }
2603
+ },
2604
+ [router]
2605
+ );
2606
+ const redirectToSignUp = useCallback(
2607
+ (returnUrl) => {
2608
+ if (returnUrl) {
2609
+ router.replace(`/sign-up?returnUrl=${encodeURIComponent(returnUrl)}`);
2610
+ } else {
2611
+ router.replace("/sign-up");
2612
+ }
2613
+ },
2614
+ [router]
2615
+ );
2616
+ return { redirectToSignIn, redirectToSignUp };
2617
+ }
2618
+ function useBiometric() {
2619
+ const checkAvailability = async () => {
2620
+ const hasHardware = await LocalAuthentication.hasHardwareAsync();
2621
+ const isEnrolled = await LocalAuthentication.isEnrolledAsync();
2622
+ const supportedTypes = await LocalAuthentication.supportedAuthenticationTypesAsync();
2623
+ return { hasHardware, isEnrolled, supportedTypes };
2624
+ };
2625
+ const authenticate = useMutation({
2626
+ mutationFn: async (promptMessage) => {
2627
+ const result = await LocalAuthentication.authenticateAsync({
2628
+ promptMessage,
2629
+ fallbackLabel: "",
2630
+ cancelLabel: "Cancel"
2631
+ });
2632
+ if (!result.success) {
2633
+ throw new RequestFailedError({ message: result.error ?? "BIOMETRIC_FAILED_ERROR" });
2634
+ }
2635
+ return result;
2636
+ }
2637
+ });
2638
+ return { checkAvailability, authenticate };
2639
+ }
2640
+ function useLogout() {
2641
+ const client = useAuthClient();
2642
+ const queryClient = useQueryClient();
2643
+ return useMutation({
2644
+ mutationFn: async () => {
2645
+ const response = await client.signOut();
2646
+ if (response.error) {
2647
+ throw new RequestFailedError({ message: response.error.message ?? "Logout failed" });
2648
+ }
2649
+ return response.data;
2650
+ },
2651
+ onSuccess: () => {
2652
+ queryClient.setQueryData(authKeys.user(), null);
2653
+ queryClient.setQueryData(authKeys.session(), null);
2654
+ queryClient.invalidateQueries({ queryKey: authKeys.sessions() });
2655
+ }
2656
+ });
2657
+ }
2658
+
1
2659
  // src/index.ts
2
- var VERSION = "0.0.1";
2660
+ var VERSION = "0.1.0";
3
2661
 
4
- export { VERSION };
2662
+ export { AuthContainer, AuthGate, AuthProvider, AuthView, ChangeEmailScreen, ChangePasswordScreen, DeleteAccountScreen, EmailVerificationScreen, ErrorFeedback, ForgotPasswordScreen, FormInput, FormPasswordInput, LoginScreen, MagicLinkScreen, OAuthButton, OrganizationMembersScreen, OrganizationSwitcherScreen, PhoneLoginScreen, ProtectedScreen, ResetPasswordScreen, SessionsScreen, SignupScreen, SubmitButton, SuccessFeedback, TwoFactorScreen, TwoFactorSettingsScreen, UpdateAvatarScreen, UpdateNameScreen, UpdateUsernameScreen, VERSION, useAuthClient, useAuthLocalization, useAuthRedirect, useBiometric, useLogin, useLogout, useRequestPasswordReset, useResetPassword, useSession, useSignup, useUser };
5
2663
  //# sourceMappingURL=index.js.map
6
2664
  //# sourceMappingURL=index.js.map