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