@atzentis/auth-react 0.0.13 → 0.0.15

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,8 +1,17 @@
1
- import { AuthClient } from '@atzentis/auth-sdk';
2
- import { createContext, useState, useCallback, useMemo, useContext } from 'react';
3
- import { jsx } from 'react/jsx-runtime';
1
+ import { AuthClient, AuthError, AuthErrorCode } from '@atzentis/auth-sdk';
2
+ import { createContext, useState, useCallback, useMemo, useContext, useEffect, useRef } from 'react';
3
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
+ import { DropdownMenu, DropdownMenuTrigger, Button, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, Card, CardHeader, CardTitle, CardDescription, CardContent, DropdownMenuLabel } from '@atzentis/ui-shadcn';
5
+ import { OTPInput, FormInput, FormSelect, FormPasswordInput } from '@atzentis/ui-shared';
6
+ import { zodResolver } from '@hookform/resolvers/zod';
7
+ import { useForm } from 'react-hook-form';
8
+ import { z } from 'zod';
4
9
 
5
10
  var AuthContext = createContext(null);
11
+ var LocalizationContext = createContext({
12
+ overrides: null,
13
+ localizeErrors: true
14
+ });
6
15
  function AuthProvider({
7
16
  apiKey,
8
17
  baseUrl,
@@ -15,7 +24,9 @@ function AuthProvider({
15
24
  onNewDeviceAlert,
16
25
  children,
17
26
  initialUser,
18
- storage
27
+ storage,
28
+ localization,
29
+ localizeErrors = true
19
30
  }) {
20
31
  const [client] = useState(() => {
21
32
  const handleSessionExpired = () => {
@@ -58,7 +69,429 @@ function AuthProvider({
58
69
  }),
59
70
  [client, user, session, isLoading, error, clearError]
60
71
  );
61
- return /* @__PURE__ */ jsx(AuthContext.Provider, { value, children });
72
+ const localizationValue = useMemo(
73
+ () => ({ overrides: localization ?? null, localizeErrors }),
74
+ [localization, localizeErrors]
75
+ );
76
+ return /* @__PURE__ */ jsx(AuthContext.Provider, { value, children: /* @__PURE__ */ jsx(LocalizationContext.Provider, { value: localizationValue, children }) });
77
+ }
78
+
79
+ // src/localization/error-codes.ts
80
+ var AUTH_ERROR_CODES = {
81
+ INVALID_CREDENTIALS: "Invalid email or password",
82
+ EMAIL_NOT_VERIFIED: "Email address is not verified",
83
+ TWO_FACTOR_REQUIRED: "Two-factor authentication is required",
84
+ INVALID_PHONE_NUMBER: "Invalid phone number",
85
+ OTP_EXPIRED: "Verification code has expired",
86
+ OTP_INVALID: "Invalid verification code",
87
+ OTP_MAX_ATTEMPTS: "Too many verification attempts",
88
+ PHONE_NUMBER_IN_USE: "Phone number is already in use",
89
+ FRAUD_DETECTED: "Suspicious activity detected",
90
+ USERNAME_TAKEN: "Username is already taken",
91
+ USERNAME_INVALID: "Invalid username",
92
+ USERNAME_RESERVED: "This username is reserved",
93
+ DEVICE_NOT_FOUND: "Device not found",
94
+ LOGIN_EVENT_NOT_FOUND: "Login event not found",
95
+ REAUTH_REQUIRED: "Please sign in again to continue",
96
+ MAX_SESSIONS_EXCEEDED: "Maximum number of sessions exceeded",
97
+ CAPTCHA_REQUIRED: "CAPTCHA verification is required",
98
+ CAPTCHA_INVALID: "CAPTCHA verification failed",
99
+ PASSWORD_BREACHED: "This password has been found in a data breach",
100
+ SESSION_EXPIRED: "Session expired",
101
+ TOKEN_EXPIRED: "Token has expired",
102
+ INVALID_TOKEN: "Invalid token",
103
+ UNAUTHORIZED_ROLE: "You do not have permission to perform this action",
104
+ ORGANIZATION_NOT_FOUND: "Organization not found",
105
+ PERMISSION_DENIED: "Permission denied",
106
+ RATE_LIMITED: "Too many requests. Please try again later.",
107
+ NETWORK_ERROR: "Network error. Please check your connection.",
108
+ TIMEOUT: "Request timed out. Please try again.",
109
+ USER_NOT_FOUND: "User not found",
110
+ USER_ALREADY_EXISTS: "A user with this email already exists",
111
+ INVALID_EMAIL: "Invalid email address",
112
+ WEAK_PASSWORD: "Password is too weak",
113
+ ORG_LIMIT_EXCEEDED: "Organization limit exceeded",
114
+ MEMBER_LIMIT_EXCEEDED: "Member limit exceeded",
115
+ KEY_NOT_FOUND: "API key not found",
116
+ KEY_REVOKED: "API key has been revoked",
117
+ KEY_EXPIRED: "API key has expired"
118
+ };
119
+
120
+ // src/localization/auth-localization.ts
121
+ var authLocalization = {
122
+ // --- General / Shared ---
123
+ /** @default "Continue" */
124
+ CONTINUE: "Continue",
125
+ /** @default "Cancel" */
126
+ CANCEL: "Cancel",
127
+ /** @default "Save" */
128
+ SAVE: "Save",
129
+ /** @default "Delete" */
130
+ DELETE: "Delete",
131
+ /** @default "Update" */
132
+ UPDATE: "Update",
133
+ /** @default "Remove" */
134
+ REMOVE: "Remove",
135
+ /** @default "Done" */
136
+ DONE: "Done",
137
+ /** @default "Go back" */
138
+ GO_BACK: "Go back",
139
+ /** @default "Loading..." */
140
+ LOADING: "Loading...",
141
+ /** @default "Load more" */
142
+ LOAD_MORE: "Load more",
143
+ /** @default "Unknown" */
144
+ UNKNOWN: "Unknown",
145
+ /** @default "Request failed" */
146
+ REQUEST_FAILED: "Request failed",
147
+ /** @default "Updated successfully" */
148
+ UPDATED_SUCCESSFULLY: "Updated successfully",
149
+ // --- Sign In (LoginForm) ---
150
+ /** @default "Sign in" */
151
+ SIGN_IN: "Sign in",
152
+ /** @default "Sign in" */
153
+ SIGN_IN_ACTION: "Sign in",
154
+ /** @default "Enter your email below to sign in" */
155
+ SIGN_IN_DESCRIPTION: "Enter your email below to sign in",
156
+ /** @default "Enter your username or email to sign in" */
157
+ SIGN_IN_USERNAME_DESCRIPTION: "Enter your username or email to sign in",
158
+ /** @default "Username or email" */
159
+ SIGN_IN_USERNAME_PLACEHOLDER: "Username or email",
160
+ /** @default "Signing in..." */
161
+ SIGNING_IN: "Signing in...",
162
+ /** @default "m@example.com" */
163
+ EMAIL_PLACEHOLDER: "m@example.com",
164
+ /** @default "Password" */
165
+ PASSWORD_PLACEHOLDER: "Password",
166
+ /** @default "Forgot your password?" */
167
+ FORGOT_PASSWORD_LINK: "Forgot your password?",
168
+ /** @default "Use phone instead" */
169
+ USE_PHONE_INSTEAD: "Use phone instead",
170
+ /** @default "Use email instead" */
171
+ USE_EMAIL_INSTEAD: "Use email instead",
172
+ /** @default "Or continue with" */
173
+ OR_CONTINUE_WITH: "Or continue with",
174
+ /** @default "Sign in with" */
175
+ SIGN_IN_WITH: "Sign in with",
176
+ /** @default "Don't have an account?" */
177
+ DONT_HAVE_AN_ACCOUNT: "Don't have an account?",
178
+ /** @default "Remember me" */
179
+ REMEMBER_ME: "Remember me",
180
+ // --- Sign Up (SignupForm) ---
181
+ /** @default "Sign up" */
182
+ SIGN_UP: "Sign up",
183
+ /** @default "Create account" */
184
+ SIGN_UP_ACTION: "Create account",
185
+ /** @default "Enter your information to create an account" */
186
+ SIGN_UP_DESCRIPTION: "Enter your information to create an account",
187
+ /** @default "Check your email for the verification link." */
188
+ SIGN_UP_EMAIL: "Check your email for the verification link.",
189
+ /** @default "Name" */
190
+ NAME_PLACEHOLDER: "Name",
191
+ /** @default "Please enter your full name." */
192
+ NAME_DESCRIPTION: "Please enter your full name.",
193
+ /** @default "Username" */
194
+ USERNAME_PLACEHOLDER: "Username",
195
+ /** @default "Username" */
196
+ USERNAME: "Username",
197
+ /** @default "Confirm password" */
198
+ CONFIRM_PASSWORD_PLACEHOLDER: "Confirm password",
199
+ /** @default "Already have an account?" */
200
+ ALREADY_HAVE_AN_ACCOUNT: "Already have an account?",
201
+ /** @default "I accept the terms and conditions" */
202
+ ACCEPT_TERMS: "I accept the terms and conditions",
203
+ /** @default "You must accept the terms" */
204
+ ACCEPT_TERMS_ERROR: "You must accept the terms",
205
+ /** @default "By continuing, you agree to the" */
206
+ BY_CONTINUING_YOU_AGREE: "By continuing, you agree to the",
207
+ /** @default "Terms of Service" */
208
+ TERMS_OF_SERVICE: "Terms of Service",
209
+ /** @default "Privacy Policy" */
210
+ PRIVACY_POLICY: "Privacy Policy",
211
+ // --- Phone Login (PhoneLoginForm) ---
212
+ /** @default "Phone number" */
213
+ PHONE_NUMBER_PLACEHOLDER: "Phone number",
214
+ /** @default "Country code" */
215
+ COUNTRY_CODE: "Country code",
216
+ /** @default "Send code" */
217
+ SEND_VERIFICATION_CODE: "Send code",
218
+ /** @default "Sending code..." */
219
+ SENDING_VERIFICATION_CODE: "Sending code...",
220
+ /** @default "One-Time Password" */
221
+ ONE_TIME_PASSWORD: "One-Time Password",
222
+ /** @default "Verification code" */
223
+ VERIFICATION_CODE_LABEL: "Verification code",
224
+ /** @default "Verify" */
225
+ VERIFY: "Verify",
226
+ /** @default "Verifying..." */
227
+ VERIFYING: "Verifying...",
228
+ /** @default "Code expires in" */
229
+ CODE_EXPIRES_IN: "Code expires in",
230
+ /** @default "Resend code" */
231
+ RESEND_CODE: "Resend code",
232
+ /** @default "Sign in with password" */
233
+ USE_PASSWORD_INSTEAD: "Sign in with password",
234
+ // --- OAuth ---
235
+ /** @default "Continue with" */
236
+ CONTINUE_WITH: "Continue with",
237
+ /** @default "Redirecting..." */
238
+ REDIRECTING: "Redirecting...",
239
+ // --- Magic Link ---
240
+ /** @default "Magic Link" */
241
+ MAGIC_LINK: "Magic Link",
242
+ /** @default "Send magic link" */
243
+ MAGIC_LINK_ACTION: "Send magic link",
244
+ /** @default "Enter your email to receive a magic link" */
245
+ MAGIC_LINK_DESCRIPTION: "Enter your email to receive a magic link",
246
+ /** @default "We sent a magic link to" */
247
+ MAGIC_LINK_EMAIL: "We sent a magic link to",
248
+ /** @default "Check your inbox and click the link to sign in." */
249
+ MAGIC_LINK_HINT: "Check your inbox and click the link to sign in.",
250
+ /** @default "Sending..." */
251
+ SENDING: "Sending...",
252
+ /** @default "Resend" */
253
+ RESEND: "Resend",
254
+ /** @default "Resending..." */
255
+ RESENDING: "Resending...",
256
+ // --- Password Management ---
257
+ /** @default "Password" */
258
+ PASSWORD: "Password",
259
+ /** @default "New Password" */
260
+ NEW_PASSWORD: "New Password",
261
+ /** @default "Current Password" */
262
+ CURRENT_PASSWORD: "Current Password",
263
+ /** @default "Change Password" */
264
+ CHANGE_PASSWORD: "Change Password",
265
+ /** @default "Enter your current and new password" */
266
+ CHANGE_PASSWORD_DESCRIPTION: "Enter your current and new password",
267
+ /** @default "Password has been changed" */
268
+ CHANGE_PASSWORD_SUCCESS: "Password has been changed",
269
+ /** @default "Forgot Password" */
270
+ FORGOT_PASSWORD: "Forgot Password",
271
+ /** @default "Send reset link" */
272
+ FORGOT_PASSWORD_ACTION: "Send reset link",
273
+ /** @default "Enter your email to reset your password" */
274
+ FORGOT_PASSWORD_DESCRIPTION: "Enter your email to reset your password",
275
+ /** @default "Check your email for the reset link" */
276
+ FORGOT_PASSWORD_EMAIL: "Check your email for the reset link",
277
+ /** @default "Reset Password" */
278
+ RESET_PASSWORD: "Reset Password",
279
+ /** @default "Save new password" */
280
+ RESET_PASSWORD_ACTION: "Save new password",
281
+ /** @default "Password reset successfully" */
282
+ RESET_PASSWORD_SUCCESS: "Password reset successfully",
283
+ /** @default "Confirm new password" */
284
+ CONFIRM_NEW_PASSWORD: "Confirm new password",
285
+ /** @default "Set Password" */
286
+ SET_PASSWORD: "Set Password",
287
+ /** @default "Click the button to receive an email to set your password" */
288
+ SET_PASSWORD_DESCRIPTION: "Click the button to receive an email to set your password",
289
+ // --- Forgot Password (P11) ---
290
+ /** @default "Check your email" */
291
+ CHECK_YOUR_EMAIL: "Check your email",
292
+ /** @default "Resend in {{seconds}}s" */
293
+ RESEND_IN: "Resend in {{seconds}}s",
294
+ // --- Email Verification (P11) ---
295
+ /** @default "Email Verification" */
296
+ EMAIL_VERIFICATION: "Email Verification",
297
+ /** @default "Verify your email address" */
298
+ EMAIL_VERIFICATION_DESCRIPTION: "Verify your email address",
299
+ /** @default "Email verified successfully" */
300
+ EMAIL_VERIFICATION_SUCCESS: "Email verified successfully",
301
+ /** @default "Verifying your email..." */
302
+ EMAIL_VERIFICATION_CHECKING: "Verifying your email...",
303
+ /** @default "Resend verification email" */
304
+ RESEND_VERIFICATION_EMAIL: "Resend verification email",
305
+ /** @default "Verification email sent" */
306
+ VERIFICATION_EMAIL_SENT: "Verification email sent",
307
+ // --- Email OTP (P11) ---
308
+ /** @default "Email Code Verification" */
309
+ EMAIL_OTP: "Email Code Verification",
310
+ /** @default "Enter your email to receive a verification code" */
311
+ EMAIL_OTP_DESCRIPTION: "Enter your email to receive a verification code",
312
+ /** @default "We sent a code to" */
313
+ EMAIL_OTP_SENT: "We sent a code to",
314
+ /** @default "Enter the 6-digit code" */
315
+ ENTER_VERIFICATION_CODE: "Enter the 6-digit code",
316
+ // --- Account Recovery ---
317
+ /** @default "Recover Account" */
318
+ RECOVER_ACCOUNT: "Recover Account",
319
+ /** @default "Recover access to your account" */
320
+ RECOVER_ACCOUNT_DESCRIPTION: "Recover access to your account",
321
+ /** @default "Select recovery method" */
322
+ SELECT_RECOVERY_METHOD: "Select recovery method",
323
+ /** @default "Reset via email link" */
324
+ RESET_VIA_EMAIL: "Reset via email link",
325
+ /** @default "Verify with email code" */
326
+ VERIFY_WITH_CODE: "Verify with email code",
327
+ /** @default "Verify via phone" */
328
+ VERIFY_VIA_PHONE: "Verify via phone",
329
+ /** @default "Step {{current}} of {{total}}" */
330
+ STEP_OF: "Step {{current}} of {{total}}",
331
+ // --- Devices & Sessions ---
332
+ /** @default "Current Session" */
333
+ CURRENT_SESSION: "Current Session",
334
+ /** @default "Current device" */
335
+ CURRENT_DEVICE: "Current device",
336
+ /** @default "Sessions" */
337
+ SESSIONS: "Sessions",
338
+ /** @default "Manage your active sessions" */
339
+ SESSIONS_DESCRIPTION: "Manage your active sessions",
340
+ /** @default "Trust" */
341
+ TRUST_DEVICE: "Trust",
342
+ /** @default "Untrust" */
343
+ UNTRUST_DEVICE: "Untrust",
344
+ /** @default "Loading devices..." */
345
+ LOADING_DEVICES: "Loading devices...",
346
+ /** @default "No devices found" */
347
+ NO_DEVICES: "No devices found",
348
+ /** @default "Last used:" */
349
+ LAST_USED: "Last used:",
350
+ /** @default "IP:" */
351
+ IP_LABEL: "IP:",
352
+ /** @default "Security" */
353
+ SECURITY: "Security",
354
+ /** @default "Session not fresh. Please sign in again." */
355
+ SESSION_NOT_FRESH: "Session not fresh. Please sign in again.",
356
+ // --- Login Activity & Risk ---
357
+ /** @default "Login Activity" */
358
+ LOGIN_ACTIVITY: "Login Activity",
359
+ /** @default "Recent login attempts" */
360
+ LOGIN_ACTIVITY_DESCRIPTION: "Recent login attempts",
361
+ /** @default "Low" */
362
+ RISK_LOW: "Low",
363
+ /** @default "Medium" */
364
+ RISK_MEDIUM: "Medium",
365
+ /** @default "High" */
366
+ RISK_HIGH: "High",
367
+ /** @default "This was me" */
368
+ MARK_RECOGNIZED: "This was me",
369
+ /** @default "Not me" */
370
+ REPORT_SUSPICIOUS: "Not me",
371
+ /** @default "Loading activity..." */
372
+ LOADING_ACTIVITY: "Loading activity...",
373
+ /** @default "No login activity found" */
374
+ NO_ACTIVITY: "No login activity found",
375
+ // --- Accounts & Switching ---
376
+ /** @default "Accounts" */
377
+ ACCOUNTS: "Accounts",
378
+ /** @default "Manage signed in accounts" */
379
+ ACCOUNTS_DESCRIPTION: "Manage signed in accounts",
380
+ /** @default "Add account" */
381
+ ADD_ACCOUNT: "Add account",
382
+ /** @default "Switch Account" */
383
+ SWITCH_ACCOUNT: "Switch Account",
384
+ /** @default "Select account" */
385
+ SELECT_ACCOUNT: "Select account",
386
+ /** @default "Personal Account" */
387
+ PERSONAL_ACCOUNT: "Personal Account",
388
+ /** @default "Sign out" */
389
+ SIGN_OUT: "Sign out",
390
+ // --- Organizations ---
391
+ /** @default "Organization" */
392
+ ORGANIZATION: "Organization",
393
+ /** @default "Organizations" */
394
+ ORGANIZATIONS: "Organizations",
395
+ /** @default "Manage your organizations" */
396
+ ORGANIZATIONS_DESCRIPTION: "Manage your organizations",
397
+ /** @default "Select organization" */
398
+ SELECT_ORGANIZATION: "Select organization",
399
+ /** @default "Create organization" */
400
+ CREATE_ORGANIZATION: "Create organization",
401
+ /** @default "Name" */
402
+ ORGANIZATION_NAME: "Name",
403
+ /** @default "Acme Inc." */
404
+ ORGANIZATION_NAME_PLACEHOLDER: "Acme Inc.",
405
+ /** @default "Leave Organization" */
406
+ LEAVE_ORGANIZATION: "Leave Organization",
407
+ /** @default "Delete Organization" */
408
+ DELETE_ORGANIZATION: "Delete Organization",
409
+ /** @default "Members" */
410
+ MEMBERS: "Members",
411
+ /** @default "Invite Member" */
412
+ INVITE_MEMBER: "Invite Member",
413
+ /** @default "Remove Member" */
414
+ REMOVE_MEMBER: "Remove Member",
415
+ /** @default "Role" */
416
+ ROLE: "Role",
417
+ /** @default "Admin" */
418
+ ADMIN: "Admin",
419
+ /** @default "Member" */
420
+ MEMBER: "Member",
421
+ /** @default "Owner" */
422
+ OWNER: "Owner",
423
+ // --- User Menu ---
424
+ /** @default "Settings" */
425
+ SETTINGS: "Settings",
426
+ /** @default "Account" */
427
+ ACCOUNT: "Account",
428
+ /** @default "Avatar" */
429
+ AVATAR: "Avatar",
430
+ /** @default "Click avatar to upload a custom one" */
431
+ AVATAR_DESCRIPTION: "Click avatar to upload a custom one",
432
+ /** @default "Delete Avatar" */
433
+ DELETE_AVATAR: "Delete Avatar",
434
+ /** @default "Upload Avatar" */
435
+ UPLOAD_AVATAR: "Upload Avatar",
436
+ /** @default "Email" */
437
+ EMAIL: "Email",
438
+ /** @default "Name" */
439
+ NAME: "Name",
440
+ // --- Two-Factor ---
441
+ /** @default "Two-Factor" */
442
+ TWO_FACTOR: "Two-Factor",
443
+ /** @default "Two-Factor Authentication" */
444
+ TWO_FACTOR_PROMPT: "Two-Factor Authentication",
445
+ /** @default "Enter your one-time password" */
446
+ TWO_FACTOR_DESCRIPTION: "Enter your one-time password",
447
+ /** @default "Verify code" */
448
+ TWO_FACTOR_ACTION: "Verify code",
449
+ /** @default "Two-factor authentication has been enabled" */
450
+ TWO_FACTOR_ENABLED: "Two-factor authentication has been enabled",
451
+ /** @default "Two-factor authentication has been disabled" */
452
+ TWO_FACTOR_DISABLED: "Two-factor authentication has been disabled",
453
+ /** @default "Enable Two-Factor" */
454
+ ENABLE_TWO_FACTOR: "Enable Two-Factor",
455
+ /** @default "Disable Two-Factor" */
456
+ DISABLE_TWO_FACTOR: "Disable Two-Factor",
457
+ /** @default "Backup Codes" */
458
+ BACKUP_CODES: "Backup Codes",
459
+ /** @default "Save these codes in a secure place" */
460
+ BACKUP_CODES_DESCRIPTION: "Save these codes in a secure place",
461
+ // --- Two-Factor (P11) ---
462
+ /** @default "Enter your 6-digit code" */
463
+ ENTER_TOTP_CODE: "Enter your 6-digit code",
464
+ /** @default "Use backup code" */
465
+ USE_BACKUP_CODE: "Use backup code",
466
+ /** @default "Use authenticator" */
467
+ USE_AUTHENTICATOR: "Use authenticator",
468
+ /** @default "Enter backup code" */
469
+ BACKUP_CODE_PLACEHOLDER: "Enter backup code",
470
+ // --- Validation ---
471
+ /** @default "Passwords do not match" */
472
+ PASSWORDS_DO_NOT_MATCH: "Passwords do not match",
473
+ /** @default "Password is required" */
474
+ PASSWORD_REQUIRED: "Password is required",
475
+ /** @default "Email address is required" */
476
+ EMAIL_REQUIRED: "Email address is required",
477
+ /** @default "is required" */
478
+ IS_REQUIRED: "is required",
479
+ /** @default "is invalid" */
480
+ IS_INVALID: "is invalid",
481
+ // --- Error Codes ---
482
+ ...AUTH_ERROR_CODES
483
+ };
484
+ function getLocalizedError(error, localization, localizeErrors) {
485
+ if (!localizeErrors) {
486
+ if (error instanceof Error && error.message) return error.message;
487
+ return authLocalization.REQUEST_FAILED;
488
+ }
489
+ if (error instanceof AuthError) {
490
+ const errorCode = error.code;
491
+ if (localization?.[errorCode]) return localization[errorCode];
492
+ }
493
+ if (error instanceof Error && error.message) return error.message;
494
+ return localization?.REQUEST_FAILED ?? authLocalization.REQUEST_FAILED;
62
495
  }
63
496
  function useAuthContext() {
64
497
  const context = useContext(AuthContext);
@@ -230,27 +663,62 @@ function useAuth() {
230
663
  }
231
664
  return {};
232
665
  }, [client]);
666
+ const requestPasswordReset = useCallback(
667
+ async (request) => {
668
+ await client.requestPasswordReset(request);
669
+ },
670
+ [client]
671
+ );
672
+ const resetPassword = useCallback(
673
+ async (request) => {
674
+ return client.resetPassword(request);
675
+ },
676
+ [client]
677
+ );
678
+ const sendVerificationEmail = useCallback(
679
+ async (request) => {
680
+ await client.sendVerificationEmail(request);
681
+ },
682
+ [client]
683
+ );
684
+ const verifyEmail = useCallback(
685
+ async (request) => {
686
+ const response = await client.verifyEmail(request);
687
+ setUser(response.user);
688
+ return response;
689
+ },
690
+ [client, setUser]
691
+ );
233
692
  return {
234
693
  user,
235
694
  isAuthenticated,
236
695
  isLoading,
237
696
  error,
697
+ clearError,
698
+ getAccessToken,
699
+ getAuthHeader,
700
+ getOAuthUrl,
701
+ isUsernameAvailable,
238
702
  login,
239
703
  loginWithUsername,
240
- signup,
241
704
  logout,
242
705
  logoutAllDevices,
243
- getOAuthUrl,
244
- verifyOAuthCode,
706
+ refreshToken,
707
+ requestPasswordReset,
708
+ resetPassword,
245
709
  sendMagicLink,
710
+ sendVerificationEmail,
711
+ signup,
712
+ verifyEmail,
246
713
  verifyMagicLink,
247
- isUsernameAvailable,
248
- getAccessToken,
249
- refreshToken,
250
- getAuthHeader,
251
- clearError
714
+ verifyOAuthCode
252
715
  };
253
716
  }
717
+ function useAuthLocalization() {
718
+ const ctx = useContext(LocalizationContext);
719
+ const localization = ctx.overrides ? { ...authLocalization, ...ctx.overrides } : authLocalization;
720
+ return { localization, localizeErrors: ctx.localizeErrors };
721
+ }
254
722
  function useDevices() {
255
723
  const { client } = useAuthContext();
256
724
  const [devices, setDevices] = useState([]);
@@ -748,6 +1216,2449 @@ function useUser() {
748
1216
  };
749
1217
  }
750
1218
 
751
- export { AuthContext, AuthProvider, useApiKeys, useAuth, useAuthContext, useDevices, useLoginActivity, useOrganizations, usePhone, useSession, useSessions, useUser };
1219
+ // src/helpers/initials.ts
1220
+ function getInitials(name) {
1221
+ return name.split(" ").map((w) => w[0]).join("").toUpperCase().slice(0, 2);
1222
+ }
1223
+ function AccountSwitcher({
1224
+ showAddAccount = false,
1225
+ onSwitch,
1226
+ onAddAccount,
1227
+ maxAccounts = 5,
1228
+ className
1229
+ }) {
1230
+ const { deviceSessions, switchAccount, isLoading, refresh } = useSessions();
1231
+ const { localization } = useAuthLocalization();
1232
+ useEffect(() => {
1233
+ refresh();
1234
+ }, [refresh]);
1235
+ const handleSwitch = useCallback(
1236
+ async (sessionToken, user) => {
1237
+ await switchAccount(sessionToken);
1238
+ onSwitch?.(user);
1239
+ },
1240
+ [switchAccount, onSwitch]
1241
+ );
1242
+ const visibleSessions = deviceSessions.slice(0, maxAccounts);
1243
+ const activeSession = deviceSessions.find((s) => s.isActive);
1244
+ return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [
1245
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "outline", disabled: isLoading, children: activeSession ? activeSession.user.name : localization.SELECT_ACCOUNT }) }),
1246
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", children: [
1247
+ visibleSessions.map((session) => /* @__PURE__ */ jsx(
1248
+ DropdownMenuItem,
1249
+ {
1250
+ onClick: () => handleSwitch(session.sessionToken, session.user),
1251
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1252
+ /* @__PURE__ */ jsx("span", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-gray-200 text-xs font-medium", children: session.user.avatar ? /* @__PURE__ */ jsx("img", { src: session.user.avatar, alt: "", className: "h-8 w-8 rounded-full" }) : getInitials(session.user.name) }),
1253
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
1254
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: session.user.name }),
1255
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-500", children: session.user.email })
1256
+ ] }),
1257
+ session.isActive && /* @__PURE__ */ jsx("span", { className: "ml-auto text-sm", children: "\u2713" })
1258
+ ] })
1259
+ },
1260
+ session.sessionToken
1261
+ )),
1262
+ showAddAccount && /* @__PURE__ */ jsxs(Fragment, { children: [
1263
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
1264
+ /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: onAddAccount, children: localization.ADD_ACCOUNT })
1265
+ ] })
1266
+ ] })
1267
+ ] }) });
1268
+ }
1269
+ function SessionGuard({
1270
+ children,
1271
+ fallback,
1272
+ loadingFallback = null,
1273
+ freshRequired = false,
1274
+ freshThreshold = 300,
1275
+ requiredRole,
1276
+ className
1277
+ }) {
1278
+ const { user, session, isAuthenticated, isLoading } = useSession();
1279
+ if (isLoading) {
1280
+ return className ? /* @__PURE__ */ jsx("div", { className, children: loadingFallback }) : /* @__PURE__ */ jsx(Fragment, { children: loadingFallback });
1281
+ }
1282
+ if (!isAuthenticated) {
1283
+ return className ? /* @__PURE__ */ jsx("div", { className, children: fallback }) : /* @__PURE__ */ jsx(Fragment, { children: fallback });
1284
+ }
1285
+ if (freshRequired && session) {
1286
+ const sessionAge = (Date.now() - new Date(session.createdAt).getTime()) / 1e3;
1287
+ if (sessionAge > freshThreshold) {
1288
+ return className ? /* @__PURE__ */ jsx("div", { className, children: fallback }) : /* @__PURE__ */ jsx(Fragment, { children: fallback });
1289
+ }
1290
+ }
1291
+ if (requiredRole && user) {
1292
+ const userRole = user.role;
1293
+ if (userRole !== requiredRole) {
1294
+ return className ? /* @__PURE__ */ jsx("div", { className, children: fallback }) : /* @__PURE__ */ jsx(Fragment, { children: fallback });
1295
+ }
1296
+ }
1297
+ return className ? /* @__PURE__ */ jsx("div", { className, children }) : /* @__PURE__ */ jsx(Fragment, { children });
1298
+ }
1299
+
1300
+ // src/components/auth/auth-view-paths.ts
1301
+ var authViewPaths = {
1302
+ "/sign-in": "/sign-in",
1303
+ "/sign-up": "/sign-up",
1304
+ "/forgot-password": "/forgot-password",
1305
+ "/reset-password": "/reset-password",
1306
+ "/verify-email": "/verify-email",
1307
+ "/email-otp": "/email-otp",
1308
+ "/recover-account": "/recover-account",
1309
+ "/two-factor": "/two-factor"
1310
+ };
1311
+ function toAuthError(error) {
1312
+ if (error instanceof AuthError) return error;
1313
+ const message = error instanceof Error ? error.message : String(error);
1314
+ return new AuthError({
1315
+ code: AuthErrorCode.NetworkError,
1316
+ message,
1317
+ statusCode: 0
1318
+ });
1319
+ }
1320
+ var emailOtpEmailSchema = z.object({
1321
+ email: z.string().min(1).email()
1322
+ });
1323
+ var emailOtpCodeSchema = z.object({
1324
+ code: z.string().length(6).regex(/^\d{6}$/)
1325
+ });
1326
+ function SubmitButton({ loading, loadingLabel, actionLabel, className }) {
1327
+ return /* @__PURE__ */ jsx(Button, { type: "submit", disabled: loading, className: className ?? "w-full", children: loading ? loadingLabel : actionLabel });
1328
+ }
1329
+
1330
+ // src/components/shared/utils.ts
1331
+ function isEmail(value) {
1332
+ return value.includes("@");
1333
+ }
1334
+ function maskEmail(email) {
1335
+ const [local, domain] = email.split("@");
1336
+ if (!local || !domain) return email;
1337
+ const masked = local.length <= 1 ? local : `${local[0]}${"*".repeat(Math.min(local.length - 1, 3))}`;
1338
+ return `${masked}@${domain}`;
1339
+ }
1340
+ function EmailOTPForm({
1341
+ redirectUrl,
1342
+ onSuccess,
1343
+ onError,
1344
+ resendCooldown = 60,
1345
+ autoSubmit = true,
1346
+ className
1347
+ }) {
1348
+ const { sendVerificationEmail, verifyEmail } = useAuth();
1349
+ const { localization, localizeErrors } = useAuthLocalization();
1350
+ const [step, setStep] = useState(1);
1351
+ const [email, setEmail] = useState("");
1352
+ const [isSubmitting, setIsSubmitting] = useState(false);
1353
+ const [formError, setFormError] = useState(null);
1354
+ const [cooldownRemaining, setCooldownRemaining] = useState(0);
1355
+ const codeRef = useRef("");
1356
+ const {
1357
+ register,
1358
+ handleSubmit,
1359
+ formState: { errors }
1360
+ } = useForm({
1361
+ resolver: zodResolver(emailOtpEmailSchema),
1362
+ defaultValues: { email: "" }
1363
+ });
1364
+ useEffect(() => {
1365
+ if (cooldownRemaining <= 0) return;
1366
+ const timer = setInterval(() => {
1367
+ setCooldownRemaining((prev) => Math.max(0, prev - 1));
1368
+ }, 1e3);
1369
+ return () => clearInterval(timer);
1370
+ }, [cooldownRemaining]);
1371
+ const onEmailSubmit = useCallback(
1372
+ async (data) => {
1373
+ setFormError(null);
1374
+ setIsSubmitting(true);
1375
+ try {
1376
+ await sendVerificationEmail({ email: data.email, redirectUrl });
1377
+ setEmail(data.email);
1378
+ setStep(2);
1379
+ setCooldownRemaining(resendCooldown);
1380
+ } catch (err) {
1381
+ setFormError(getLocalizedError(err, localization, localizeErrors));
1382
+ onError?.(toAuthError(err));
1383
+ } finally {
1384
+ setIsSubmitting(false);
1385
+ }
1386
+ },
1387
+ [sendVerificationEmail, redirectUrl, resendCooldown, onError, localization, localizeErrors]
1388
+ );
1389
+ const handleVerifyCode = useCallback(
1390
+ async (code) => {
1391
+ setFormError(null);
1392
+ setIsSubmitting(true);
1393
+ try {
1394
+ const response = await verifyEmail({ token: code });
1395
+ onSuccess?.(response.user);
1396
+ } catch (err) {
1397
+ setFormError(getLocalizedError(err, localization, localizeErrors));
1398
+ onError?.(toAuthError(err));
1399
+ } finally {
1400
+ setIsSubmitting(false);
1401
+ }
1402
+ },
1403
+ [verifyEmail, onSuccess, onError, localization, localizeErrors]
1404
+ );
1405
+ const handleOtpComplete = useCallback(
1406
+ (code) => {
1407
+ codeRef.current = code;
1408
+ if (autoSubmit) {
1409
+ handleVerifyCode(code);
1410
+ }
1411
+ },
1412
+ [autoSubmit, handleVerifyCode]
1413
+ );
1414
+ const handleManualVerify = useCallback(
1415
+ (e) => {
1416
+ e.preventDefault();
1417
+ if (codeRef.current) {
1418
+ handleVerifyCode(codeRef.current);
1419
+ }
1420
+ },
1421
+ [handleVerifyCode]
1422
+ );
1423
+ const handleResend = useCallback(async () => {
1424
+ setFormError(null);
1425
+ setIsSubmitting(true);
1426
+ try {
1427
+ await sendVerificationEmail({ email, redirectUrl });
1428
+ setCooldownRemaining(resendCooldown);
1429
+ } catch (err) {
1430
+ setFormError(getLocalizedError(err, localization, localizeErrors));
1431
+ } finally {
1432
+ setIsSubmitting(false);
1433
+ }
1434
+ }, [sendVerificationEmail, email, redirectUrl, resendCooldown, localization, localizeErrors]);
1435
+ const handleGoBack = useCallback(() => {
1436
+ setStep(1);
1437
+ setFormError(null);
1438
+ codeRef.current = "";
1439
+ }, []);
1440
+ if (step === 2) {
1441
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-4", children: [
1442
+ /* @__PURE__ */ jsxs("div", { className: "text-center space-y-2", children: [
1443
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: localization.EMAIL_OTP }),
1444
+ /* @__PURE__ */ jsxs("p", { className: "text-sm", children: [
1445
+ localization.EMAIL_OTP_SENT,
1446
+ " ",
1447
+ /* @__PURE__ */ jsx("strong", { children: maskEmail(email) })
1448
+ ] })
1449
+ ] }),
1450
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleManualVerify, className: "space-y-4", children: [
1451
+ /* @__PURE__ */ jsxs("label", { className: "block text-sm font-medium", children: [
1452
+ localization.ENTER_VERIFICATION_CODE,
1453
+ /* @__PURE__ */ jsx(
1454
+ OTPInput,
1455
+ {
1456
+ length: 6,
1457
+ onComplete: handleOtpComplete,
1458
+ disabled: isSubmitting,
1459
+ className: "mt-2"
1460
+ }
1461
+ )
1462
+ ] }),
1463
+ formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
1464
+ /* @__PURE__ */ jsx(
1465
+ SubmitButton,
1466
+ {
1467
+ loading: isSubmitting,
1468
+ loadingLabel: localization.VERIFYING,
1469
+ actionLabel: localization.VERIFY
1470
+ }
1471
+ ),
1472
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1473
+ /* @__PURE__ */ jsx(
1474
+ Button,
1475
+ {
1476
+ type: "button",
1477
+ variant: "outline",
1478
+ disabled: isSubmitting || cooldownRemaining > 0,
1479
+ onClick: handleResend,
1480
+ className: "w-full",
1481
+ children: cooldownRemaining > 0 ? localization.RESEND_IN.replace("{{seconds}}", String(cooldownRemaining)) : isSubmitting ? localization.RESENDING : localization.RESEND
1482
+ }
1483
+ ),
1484
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: handleGoBack, className: "w-full", children: localization.GO_BACK })
1485
+ ] })
1486
+ ] })
1487
+ ] }) });
1488
+ }
1489
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-4", children: [
1490
+ /* @__PURE__ */ jsxs("div", { className: "text-center space-y-2", children: [
1491
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: localization.EMAIL_OTP }),
1492
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: localization.EMAIL_OTP_DESCRIPTION })
1493
+ ] }),
1494
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit(onEmailSubmit), className: "space-y-4", children: [
1495
+ /* @__PURE__ */ jsx(
1496
+ FormInput,
1497
+ {
1498
+ label: localization.EMAIL,
1499
+ id: "email-otp-email",
1500
+ type: "email",
1501
+ autoComplete: "email",
1502
+ error: errors.email?.message,
1503
+ disabled: isSubmitting,
1504
+ ...register("email")
1505
+ }
1506
+ ),
1507
+ formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
1508
+ /* @__PURE__ */ jsx(
1509
+ SubmitButton,
1510
+ {
1511
+ loading: isSubmitting,
1512
+ loadingLabel: localization.SENDING,
1513
+ actionLabel: localization.SEND_VERIFICATION_CODE
1514
+ }
1515
+ )
1516
+ ] })
1517
+ ] }) });
1518
+ }
1519
+ function EmailVerificationForm({
1520
+ token,
1521
+ email,
1522
+ redirectUrl,
1523
+ onSuccess,
1524
+ onError,
1525
+ resendCooldown = 60,
1526
+ className
1527
+ }) {
1528
+ const { sendVerificationEmail, verifyEmail } = useAuth();
1529
+ const { localization, localizeErrors } = useAuthLocalization();
1530
+ const [status, setStatus] = useState(
1531
+ token ? "verifying" : "resend"
1532
+ );
1533
+ const [formError, setFormError] = useState(null);
1534
+ const [cooldownRemaining, setCooldownRemaining] = useState(0);
1535
+ const [isResending, setIsResending] = useState(false);
1536
+ useEffect(() => {
1537
+ if (!token) return;
1538
+ setStatus("verifying");
1539
+ verifyEmail({ token }).then((response) => {
1540
+ setStatus("success");
1541
+ onSuccess?.(response.user);
1542
+ }).catch((err) => {
1543
+ setStatus("error");
1544
+ setFormError(getLocalizedError(err, localization, localizeErrors));
1545
+ onError?.(toAuthError(err));
1546
+ });
1547
+ }, []);
1548
+ useEffect(() => {
1549
+ if (cooldownRemaining <= 0) return;
1550
+ const timer = setInterval(() => {
1551
+ setCooldownRemaining((prev) => {
1552
+ if (prev <= 1) {
1553
+ clearInterval(timer);
1554
+ return 0;
1555
+ }
1556
+ return prev - 1;
1557
+ });
1558
+ }, 1e3);
1559
+ return () => clearInterval(timer);
1560
+ }, [cooldownRemaining]);
1561
+ const handleResend = useCallback(async () => {
1562
+ if (!email) return;
1563
+ setFormError(null);
1564
+ setIsResending(true);
1565
+ try {
1566
+ await sendVerificationEmail({ email, redirectUrl });
1567
+ setCooldownRemaining(resendCooldown);
1568
+ } catch (err) {
1569
+ setFormError(getLocalizedError(err, localization, localizeErrors));
1570
+ onError?.(toAuthError(err));
1571
+ } finally {
1572
+ setIsResending(false);
1573
+ }
1574
+ }, [
1575
+ email,
1576
+ redirectUrl,
1577
+ resendCooldown,
1578
+ sendVerificationEmail,
1579
+ localization,
1580
+ localizeErrors,
1581
+ onError
1582
+ ]);
1583
+ if (status === "verifying") {
1584
+ return /* @__PURE__ */ jsxs(Card, { className, children: [
1585
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
1586
+ /* @__PURE__ */ jsx(CardTitle, { children: localization.EMAIL_VERIFICATION }),
1587
+ /* @__PURE__ */ jsx(CardDescription, { children: localization.EMAIL_VERIFICATION_DESCRIPTION })
1588
+ ] }),
1589
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-8", "aria-live": "polite", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-3", children: [
1590
+ /* @__PURE__ */ jsx("div", { className: "h-5 w-5 animate-spin rounded-full border-2 border-gray-300 border-t-gray-900" }),
1591
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600", children: localization.EMAIL_VERIFICATION_CHECKING })
1592
+ ] }) }) })
1593
+ ] });
1594
+ }
1595
+ if (status === "success") {
1596
+ return /* @__PURE__ */ jsxs(Card, { className, children: [
1597
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
1598
+ /* @__PURE__ */ jsx(CardTitle, { children: localization.EMAIL_VERIFICATION }),
1599
+ /* @__PURE__ */ jsx(CardDescription, { children: localization.EMAIL_VERIFICATION_DESCRIPTION })
1600
+ ] }),
1601
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs(
1602
+ "div",
1603
+ {
1604
+ className: "flex flex-col items-center justify-center py-8 space-y-4",
1605
+ "aria-live": "polite",
1606
+ children: [
1607
+ /* @__PURE__ */ jsx("div", { className: "rounded-full bg-green-100 p-3", children: /* @__PURE__ */ jsx(
1608
+ "svg",
1609
+ {
1610
+ className: "h-8 w-8 text-green-600",
1611
+ fill: "none",
1612
+ stroke: "currentColor",
1613
+ viewBox: "0 0 24 24",
1614
+ "aria-hidden": "true",
1615
+ children: /* @__PURE__ */ jsx(
1616
+ "path",
1617
+ {
1618
+ strokeLinecap: "round",
1619
+ strokeLinejoin: "round",
1620
+ strokeWidth: 2,
1621
+ d: "M5 13l4 4L19 7"
1622
+ }
1623
+ )
1624
+ }
1625
+ ) }),
1626
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-green-600", children: localization.EMAIL_VERIFICATION_SUCCESS })
1627
+ ]
1628
+ }
1629
+ ) })
1630
+ ] });
1631
+ }
1632
+ return /* @__PURE__ */ jsxs(Card, { className, children: [
1633
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
1634
+ /* @__PURE__ */ jsx(CardTitle, { children: localization.EMAIL_VERIFICATION }),
1635
+ /* @__PURE__ */ jsx(CardDescription, { children: localization.EMAIL_VERIFICATION_DESCRIPTION })
1636
+ ] }),
1637
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1638
+ email && /* @__PURE__ */ jsxs("div", { className: "text-sm text-gray-600", children: [
1639
+ localization.CHECK_YOUR_EMAIL,
1640
+ ": ",
1641
+ /* @__PURE__ */ jsx("strong", { children: maskEmail(email) })
1642
+ ] }),
1643
+ formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
1644
+ /* @__PURE__ */ jsx(
1645
+ Button,
1646
+ {
1647
+ type: "button",
1648
+ variant: "outline",
1649
+ disabled: isResending || cooldownRemaining > 0,
1650
+ onClick: handleResend,
1651
+ className: "w-full",
1652
+ children: isResending ? localization.RESENDING : cooldownRemaining > 0 ? localization.RESEND_IN.replace("{{seconds}}", cooldownRemaining.toString()) : localization.RESEND_VERIFICATION_EMAIL
1653
+ }
1654
+ )
1655
+ ] }) })
1656
+ ] });
1657
+ }
1658
+ var forgotPasswordSchema = z.object({
1659
+ email: z.string().min(1).email()
1660
+ });
1661
+ function ForgotPasswordForm({
1662
+ redirectUrl,
1663
+ onSuccess,
1664
+ onError,
1665
+ onBackToLogin,
1666
+ resendCooldown = 60,
1667
+ className
1668
+ }) {
1669
+ const { requestPasswordReset } = useAuth();
1670
+ const { localization, localizeErrors } = useAuthLocalization();
1671
+ const [sent, setSent] = useState(false);
1672
+ const [sentEmail, setSentEmail] = useState("");
1673
+ const [isSubmitting, setIsSubmitting] = useState(false);
1674
+ const [formError, setFormError] = useState(null);
1675
+ const [cooldownRemaining, setCooldownRemaining] = useState(0);
1676
+ const {
1677
+ register,
1678
+ handleSubmit,
1679
+ formState: { errors },
1680
+ getValues
1681
+ } = useForm({
1682
+ resolver: zodResolver(forgotPasswordSchema),
1683
+ defaultValues: { email: "" }
1684
+ });
1685
+ useEffect(() => {
1686
+ if (cooldownRemaining <= 0) return;
1687
+ const timer = setInterval(() => {
1688
+ setCooldownRemaining((prev) => Math.max(0, prev - 1));
1689
+ }, 1e3);
1690
+ return () => clearInterval(timer);
1691
+ }, [cooldownRemaining]);
1692
+ const onSubmit = useCallback(
1693
+ async (data) => {
1694
+ setFormError(null);
1695
+ setIsSubmitting(true);
1696
+ try {
1697
+ await requestPasswordReset({ email: data.email, redirectUrl });
1698
+ setSent(true);
1699
+ setSentEmail(data.email);
1700
+ setCooldownRemaining(resendCooldown);
1701
+ onSuccess?.();
1702
+ } catch (err) {
1703
+ setFormError(getLocalizedError(err, localization, localizeErrors));
1704
+ onError?.(toAuthError(err));
1705
+ } finally {
1706
+ setIsSubmitting(false);
1707
+ }
1708
+ },
1709
+ [
1710
+ requestPasswordReset,
1711
+ redirectUrl,
1712
+ resendCooldown,
1713
+ onSuccess,
1714
+ onError,
1715
+ localization,
1716
+ localizeErrors
1717
+ ]
1718
+ );
1719
+ const handleResend = useCallback(async () => {
1720
+ const email = getValues("email");
1721
+ setFormError(null);
1722
+ setIsSubmitting(true);
1723
+ try {
1724
+ await requestPasswordReset({ email, redirectUrl });
1725
+ setCooldownRemaining(resendCooldown);
1726
+ } catch (err) {
1727
+ setFormError(getLocalizedError(err, localization, localizeErrors));
1728
+ } finally {
1729
+ setIsSubmitting(false);
1730
+ }
1731
+ }, [requestPasswordReset, redirectUrl, resendCooldown, getValues, localization, localizeErrors]);
1732
+ if (sent) {
1733
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-4", children: [
1734
+ /* @__PURE__ */ jsxs("div", { className: "text-center space-y-2", children: [
1735
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: localization.CHECK_YOUR_EMAIL }),
1736
+ /* @__PURE__ */ jsx("p", { className: "text-sm", children: localization.FORGOT_PASSWORD_EMAIL }),
1737
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: maskEmail(sentEmail) })
1738
+ ] }),
1739
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1740
+ /* @__PURE__ */ jsx(
1741
+ Button,
1742
+ {
1743
+ type: "button",
1744
+ variant: "outline",
1745
+ disabled: isSubmitting || cooldownRemaining > 0,
1746
+ onClick: handleResend,
1747
+ className: "w-full",
1748
+ children: cooldownRemaining > 0 ? localization.RESEND_IN.replace("{{seconds}}", String(cooldownRemaining)) : isSubmitting ? localization.RESENDING : localization.RESEND
1749
+ }
1750
+ ),
1751
+ onBackToLogin && /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: onBackToLogin, className: "w-full", children: localization.GO_BACK })
1752
+ ] }),
1753
+ formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600 text-center", children: formError })
1754
+ ] }) });
1755
+ }
1756
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-4", children: [
1757
+ /* @__PURE__ */ jsxs("div", { className: "text-center space-y-2", children: [
1758
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: localization.FORGOT_PASSWORD }),
1759
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: localization.FORGOT_PASSWORD_DESCRIPTION })
1760
+ ] }),
1761
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit(onSubmit), className: "space-y-4", children: [
1762
+ /* @__PURE__ */ jsx(
1763
+ FormInput,
1764
+ {
1765
+ label: localization.EMAIL,
1766
+ id: "forgot-password-email",
1767
+ type: "email",
1768
+ autoComplete: "email",
1769
+ error: errors.email?.message,
1770
+ disabled: isSubmitting,
1771
+ ...register("email")
1772
+ }
1773
+ ),
1774
+ formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
1775
+ /* @__PURE__ */ jsx(
1776
+ SubmitButton,
1777
+ {
1778
+ loading: isSubmitting,
1779
+ loadingLabel: localization.SENDING,
1780
+ actionLabel: localization.FORGOT_PASSWORD_ACTION
1781
+ }
1782
+ ),
1783
+ onBackToLogin && /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: onBackToLogin, className: "w-full", children: localization.GO_BACK })
1784
+ ] })
1785
+ ] }) });
1786
+ }
1787
+ var credentialLoginSchema = z.object({
1788
+ identity: z.string().min(1, "Email or username is required"),
1789
+ password: z.string().min(1, "Password is required"),
1790
+ captchaToken: z.string().optional()
1791
+ });
1792
+ var phoneLoginSchema = z.object({
1793
+ countryCode: z.string(),
1794
+ phoneNumber: z.string().min(1, "Phone number is required")
1795
+ });
1796
+ var otpVerifySchema = z.object({
1797
+ code: z.string().length(6, "Code must be 6 digits")
1798
+ });
1799
+ var PROVIDER_NAMES = {
1800
+ google: "Google",
1801
+ github: "GitHub",
1802
+ apple: "Apple",
1803
+ microsoft: "Microsoft",
1804
+ atzentis: "Atzentis"
1805
+ };
1806
+ function OAuthButton({
1807
+ provider,
1808
+ redirectUri,
1809
+ state,
1810
+ variant = "outline",
1811
+ size = "md",
1812
+ className
1813
+ }) {
1814
+ const { getOAuthUrl } = useAuth();
1815
+ const { localization } = useAuthLocalization();
1816
+ const [isLoading, setIsLoading] = useState(false);
1817
+ const sizeMap = {
1818
+ sm: "sm",
1819
+ md: "default",
1820
+ lg: "lg"
1821
+ };
1822
+ const providerName = PROVIDER_NAMES[provider];
1823
+ const handleClick = useCallback(async () => {
1824
+ setIsLoading(true);
1825
+ try {
1826
+ const url = await getOAuthUrl({ provider, redirectUri, state: state ?? "" });
1827
+ window.location.href = url;
1828
+ } catch {
1829
+ setIsLoading(false);
1830
+ }
1831
+ }, [getOAuthUrl, provider, redirectUri, state]);
1832
+ return /* @__PURE__ */ jsx(
1833
+ Button,
1834
+ {
1835
+ type: "button",
1836
+ variant: variant === "filled" ? "default" : "outline",
1837
+ size: sizeMap[size],
1838
+ disabled: isLoading,
1839
+ onClick: handleClick,
1840
+ "aria-label": `${localization.SIGN_IN_WITH} ${providerName}`,
1841
+ className,
1842
+ children: isLoading ? localization.REDIRECTING : `${localization.CONTINUE_WITH} ${providerName}`
1843
+ }
1844
+ );
1845
+ }
1846
+ var phoneNumberSchema = z.object({
1847
+ countryCode: z.string(),
1848
+ phoneNumber: z.string().min(1, "Phone number is required")
1849
+ });
1850
+ var otpVerifySchema2 = z.object({
1851
+ code: z.string().length(6, "Code must be 6 digits")
1852
+ });
1853
+ var phonePasswordSchema = z.object({
1854
+ countryCode: z.string(),
1855
+ phoneNumber: z.string().min(1, "Phone number is required"),
1856
+ password: z.string().min(1, "Password is required")
1857
+ });
1858
+
1859
+ // src/components/shared/country-codes.ts
1860
+ var COUNTRY_CODES = [
1861
+ { code: "+30", country: "GR", name: "Greece", flag: "\u{1F1EC}\u{1F1F7}" },
1862
+ { code: "+49", country: "DE", name: "Germany", flag: "\u{1F1E9}\u{1F1EA}" },
1863
+ { code: "+1", country: "US", name: "United States", flag: "\u{1F1FA}\u{1F1F8}" },
1864
+ { code: "+44", country: "GB", name: "United Kingdom", flag: "\u{1F1EC}\u{1F1E7}" },
1865
+ { code: "+33", country: "FR", name: "France", flag: "\u{1F1EB}\u{1F1F7}" },
1866
+ { code: "+39", country: "IT", name: "Italy", flag: "\u{1F1EE}\u{1F1F9}" },
1867
+ { code: "+34", country: "ES", name: "Spain", flag: "\u{1F1EA}\u{1F1F8}" },
1868
+ { code: "+31", country: "NL", name: "Netherlands", flag: "\u{1F1F3}\u{1F1F1}" },
1869
+ { code: "+43", country: "AT", name: "Austria", flag: "\u{1F1E6}\u{1F1F9}" },
1870
+ { code: "+41", country: "CH", name: "Switzerland", flag: "\u{1F1E8}\u{1F1ED}" },
1871
+ { code: "+357", country: "CY", name: "Cyprus", flag: "\u{1F1E8}\u{1F1FE}" },
1872
+ { code: "+90", country: "TR", name: "Turkey", flag: "\u{1F1F9}\u{1F1F7}" }
1873
+ ];
1874
+ function PhoneLoginForm({
1875
+ onSuccess,
1876
+ onError,
1877
+ allowedCountryCodes = ["+30", "+49"],
1878
+ showPasswordFallback = false,
1879
+ className
1880
+ }) {
1881
+ const { sendOTP, verify, signIn, isSending, isVerifying, expiresIn } = usePhone();
1882
+ const { localization, localizeErrors } = useAuthLocalization();
1883
+ const [step, setStep] = useState("phone");
1884
+ const [formError, setFormError] = useState(null);
1885
+ const [fullPhoneNumber, setFullPhoneNumber] = useState("");
1886
+ const filteredCountryCodes = useMemo(
1887
+ () => COUNTRY_CODES.filter((c) => allowedCountryCodes.includes(c.code)),
1888
+ [allowedCountryCodes]
1889
+ );
1890
+ const phoneForm = useForm({
1891
+ resolver: zodResolver(phoneNumberSchema),
1892
+ defaultValues: {
1893
+ countryCode: filteredCountryCodes[0]?.code ?? "+30",
1894
+ phoneNumber: ""
1895
+ }
1896
+ });
1897
+ const otpForm = useForm({
1898
+ resolver: zodResolver(otpVerifySchema2),
1899
+ defaultValues: { code: "" }
1900
+ });
1901
+ const [passwordValue, setPasswordValue] = useState("");
1902
+ const countryCodeOptions = filteredCountryCodes.map((c) => ({
1903
+ value: c.code,
1904
+ label: `${c.flag} ${c.name} (${c.code})`
1905
+ }));
1906
+ const handlePhoneSubmit = useCallback(
1907
+ async (data) => {
1908
+ setFormError(null);
1909
+ const phone = `${data.countryCode}${data.phoneNumber}`;
1910
+ setFullPhoneNumber(phone);
1911
+ try {
1912
+ await sendOTP(phone);
1913
+ setStep("otp");
1914
+ } catch (err) {
1915
+ setFormError(getLocalizedError(err, localization, localizeErrors));
1916
+ onError?.(toAuthError(err));
1917
+ }
1918
+ },
1919
+ [sendOTP, onError, localization, localizeErrors]
1920
+ );
1921
+ const handleOtpSubmit = useCallback(
1922
+ async (data) => {
1923
+ setFormError(null);
1924
+ try {
1925
+ const response = await verify(fullPhoneNumber, data.code);
1926
+ onSuccess(response.user);
1927
+ } catch (err) {
1928
+ setFormError(getLocalizedError(err, localization, localizeErrors));
1929
+ onError?.(toAuthError(err));
1930
+ }
1931
+ },
1932
+ [verify, fullPhoneNumber, onSuccess, onError, localization, localizeErrors]
1933
+ );
1934
+ const handleOtpComplete = useCallback(
1935
+ (code) => {
1936
+ otpForm.setValue("code", code);
1937
+ otpForm.handleSubmit(handleOtpSubmit)();
1938
+ },
1939
+ [otpForm, handleOtpSubmit]
1940
+ );
1941
+ const handlePasswordSubmit = useCallback(
1942
+ async (e) => {
1943
+ e.preventDefault();
1944
+ setFormError(null);
1945
+ try {
1946
+ const response = await signIn(fullPhoneNumber, passwordValue);
1947
+ onSuccess(response.user);
1948
+ } catch (err) {
1949
+ setFormError(getLocalizedError(err, localization, localizeErrors));
1950
+ onError?.(toAuthError(err));
1951
+ }
1952
+ },
1953
+ [signIn, fullPhoneNumber, passwordValue, onSuccess, onError, localization, localizeErrors]
1954
+ );
1955
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-4", children: [
1956
+ step === "phone" && /* @__PURE__ */ jsxs("form", { onSubmit: phoneForm.handleSubmit(handlePhoneSubmit), className: "space-y-4", children: [
1957
+ /* @__PURE__ */ jsx(
1958
+ FormSelect,
1959
+ {
1960
+ label: localization.COUNTRY_CODE,
1961
+ options: countryCodeOptions,
1962
+ value: phoneForm.watch("countryCode"),
1963
+ onValueChange: (value) => phoneForm.setValue("countryCode", value)
1964
+ }
1965
+ ),
1966
+ /* @__PURE__ */ jsx(
1967
+ FormInput,
1968
+ {
1969
+ label: localization.PHONE_NUMBER_PLACEHOLDER,
1970
+ id: "phone-number",
1971
+ type: "tel",
1972
+ error: phoneForm.formState.errors.phoneNumber?.message,
1973
+ ...phoneForm.register("phoneNumber")
1974
+ }
1975
+ ),
1976
+ formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
1977
+ /* @__PURE__ */ jsx(
1978
+ SubmitButton,
1979
+ {
1980
+ loading: isSending,
1981
+ loadingLabel: localization.SENDING_VERIFICATION_CODE,
1982
+ actionLabel: localization.SEND_VERIFICATION_CODE
1983
+ }
1984
+ )
1985
+ ] }),
1986
+ step === "otp" && /* @__PURE__ */ jsxs("form", { onSubmit: otpForm.handleSubmit(handleOtpSubmit), className: "space-y-4", children: [
1987
+ /* @__PURE__ */ jsxs("label", { className: "block text-sm font-medium", children: [
1988
+ localization.VERIFICATION_CODE_LABEL,
1989
+ /* @__PURE__ */ jsx(OTPInput, { length: 6, onComplete: handleOtpComplete, disabled: isVerifying })
1990
+ ] }),
1991
+ expiresIn !== null && /* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-500", children: [
1992
+ localization.CODE_EXPIRES_IN,
1993
+ " ",
1994
+ expiresIn,
1995
+ "s"
1996
+ ] }),
1997
+ formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
1998
+ /* @__PURE__ */ jsx(
1999
+ SubmitButton,
2000
+ {
2001
+ loading: isVerifying,
2002
+ loadingLabel: localization.VERIFYING,
2003
+ actionLabel: localization.VERIFY
2004
+ }
2005
+ ),
2006
+ showPasswordFallback && /* @__PURE__ */ jsx(
2007
+ Button,
2008
+ {
2009
+ variant: "link",
2010
+ type: "button",
2011
+ className: "p-0 h-auto text-sm",
2012
+ onClick: () => setStep("password"),
2013
+ children: localization.USE_PASSWORD_INSTEAD
2014
+ }
2015
+ )
2016
+ ] }),
2017
+ step === "password" && /* @__PURE__ */ jsxs("form", { onSubmit: handlePasswordSubmit, className: "space-y-4", children: [
2018
+ /* @__PURE__ */ jsx(
2019
+ FormPasswordInput,
2020
+ {
2021
+ label: localization.PASSWORD,
2022
+ id: "phone-password",
2023
+ value: passwordValue,
2024
+ onChange: (e) => setPasswordValue(e.target.value)
2025
+ }
2026
+ ),
2027
+ formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
2028
+ /* @__PURE__ */ jsx(
2029
+ SubmitButton,
2030
+ {
2031
+ loading: isVerifying,
2032
+ loadingLabel: localization.SIGNING_IN,
2033
+ actionLabel: localization.SIGN_IN
2034
+ }
2035
+ )
2036
+ ] })
2037
+ ] }) });
2038
+ }
2039
+ function LoginForm({
2040
+ onSuccess,
2041
+ onError,
2042
+ enablePhone = false,
2043
+ enableForgotPassword = false,
2044
+ onForgotPasswordClick,
2045
+ className,
2046
+ ...oauthProps
2047
+ }) {
2048
+ const oauthProviders = "oauthProviders" in oauthProps ? oauthProps.oauthProviders : void 0;
2049
+ const oauthRedirectUri = "oauthRedirectUri" in oauthProps ? oauthProps.oauthRedirectUri : "";
2050
+ const { login, loginWithUsername } = useAuth();
2051
+ const { localization, localizeErrors } = useAuthLocalization();
2052
+ const [mode, setMode] = useState("credential");
2053
+ const [formError, setFormError] = useState(null);
2054
+ const [isSubmitting, setIsSubmitting] = useState(false);
2055
+ const {
2056
+ register,
2057
+ handleSubmit,
2058
+ watch,
2059
+ formState: { errors }
2060
+ } = useForm({
2061
+ resolver: zodResolver(credentialLoginSchema),
2062
+ defaultValues: { identity: "", password: "" }
2063
+ });
2064
+ const identityValue = watch("identity");
2065
+ const onSubmit = useCallback(
2066
+ async (data) => {
2067
+ setFormError(null);
2068
+ setIsSubmitting(true);
2069
+ try {
2070
+ if (isEmail(data.identity)) {
2071
+ const response = await login({
2072
+ email: data.identity,
2073
+ password: data.password,
2074
+ captchaToken: data.captchaToken
2075
+ });
2076
+ onSuccess(response.user);
2077
+ } else {
2078
+ const response = await loginWithUsername({
2079
+ username: data.identity,
2080
+ password: data.password,
2081
+ captchaToken: data.captchaToken
2082
+ });
2083
+ onSuccess(response.user);
2084
+ }
2085
+ } catch (err) {
2086
+ setFormError(getLocalizedError(err, localization, localizeErrors));
2087
+ onError?.(toAuthError(err));
2088
+ } finally {
2089
+ setIsSubmitting(false);
2090
+ }
2091
+ },
2092
+ [login, loginWithUsername, onSuccess, onError, localization, localizeErrors]
2093
+ );
2094
+ const handleSwitchToPhone = useCallback(() => {
2095
+ setMode("phone");
2096
+ setFormError(null);
2097
+ }, []);
2098
+ const handleSwitchToCredential = useCallback(() => {
2099
+ setMode("credential");
2100
+ setFormError(null);
2101
+ }, []);
2102
+ if (mode === "phone") {
2103
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-4", children: [
2104
+ /* @__PURE__ */ jsx(PhoneLoginForm, { onSuccess, onError }),
2105
+ /* @__PURE__ */ jsx(
2106
+ Button,
2107
+ {
2108
+ variant: "link",
2109
+ type: "button",
2110
+ className: "p-0 h-auto text-sm",
2111
+ onClick: handleSwitchToCredential,
2112
+ children: localization.USE_EMAIL_INSTEAD
2113
+ }
2114
+ )
2115
+ ] }) });
2116
+ }
2117
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit(onSubmit), className: "p-6 space-y-4", children: [
2118
+ /* @__PURE__ */ jsx(
2119
+ FormInput,
2120
+ {
2121
+ label: localization.SIGN_IN_USERNAME_PLACEHOLDER,
2122
+ id: "login-identity",
2123
+ type: "text",
2124
+ autoComplete: "username",
2125
+ error: errors.identity?.message,
2126
+ ...register("identity")
2127
+ }
2128
+ ),
2129
+ identityValue && /* @__PURE__ */ jsx(
2130
+ FormPasswordInput,
2131
+ {
2132
+ label: localization.PASSWORD,
2133
+ id: "login-password",
2134
+ autoComplete: "current-password",
2135
+ error: errors.password?.message,
2136
+ ...register("password")
2137
+ }
2138
+ ),
2139
+ formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
2140
+ /* @__PURE__ */ jsx(
2141
+ SubmitButton,
2142
+ {
2143
+ loading: isSubmitting,
2144
+ loadingLabel: localization.SIGNING_IN,
2145
+ actionLabel: localization.SIGN_IN
2146
+ }
2147
+ ),
2148
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-sm", children: [
2149
+ enableForgotPassword && /* @__PURE__ */ jsx(
2150
+ Button,
2151
+ {
2152
+ variant: "link",
2153
+ type: "button",
2154
+ className: "p-0 h-auto",
2155
+ onClick: onForgotPasswordClick,
2156
+ children: localization.FORGOT_PASSWORD_LINK
2157
+ }
2158
+ ),
2159
+ enablePhone && /* @__PURE__ */ jsx(
2160
+ Button,
2161
+ {
2162
+ variant: "link",
2163
+ type: "button",
2164
+ className: "p-0 h-auto",
2165
+ onClick: handleSwitchToPhone,
2166
+ children: localization.USE_PHONE_INSTEAD
2167
+ }
2168
+ )
2169
+ ] }),
2170
+ oauthProviders && oauthProviders.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
2171
+ /* @__PURE__ */ jsxs("div", { className: "relative flex items-center py-2", children: [
2172
+ /* @__PURE__ */ jsx("div", { className: "flex-grow border-t border-gray-300" }),
2173
+ /* @__PURE__ */ jsx("span", { className: "mx-4 text-sm text-gray-500", children: localization.OR_CONTINUE_WITH }),
2174
+ /* @__PURE__ */ jsx("div", { className: "flex-grow border-t border-gray-300" })
2175
+ ] }),
2176
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: oauthProviders.map((provider) => /* @__PURE__ */ jsx(OAuthButton, { provider, redirectUri: oauthRedirectUri }, provider)) })
2177
+ ] })
2178
+ ] }) });
2179
+ }
2180
+ var recoverEmailSchema = z.object({
2181
+ email: z.string().min(1).email()
2182
+ });
2183
+ var recoverCodeSchema = z.object({
2184
+ code: z.string().length(6).regex(/^\d{6}$/)
2185
+ });
2186
+ var recoverNewPasswordSchema = z.object({
2187
+ newPassword: z.string().min(8),
2188
+ confirmPassword: z.string().min(1)
2189
+ }).refine((data) => data.newPassword === data.confirmPassword, {
2190
+ message: "PASSWORDS_DO_NOT_MATCH",
2191
+ path: ["confirmPassword"]
2192
+ });
2193
+ var recoverPhoneSchema = z.object({
2194
+ phoneNumber: z.string().min(1)
2195
+ });
2196
+ function RecoverAccountForm({
2197
+ redirectUrl,
2198
+ enablePhone = false,
2199
+ onSuccess,
2200
+ onError,
2201
+ resendCooldown = 60,
2202
+ className
2203
+ }) {
2204
+ const { requestPasswordReset, sendVerificationEmail, verifyEmail, resetPassword } = useAuth();
2205
+ const { sendOTP, verify: verifyPhone } = usePhone();
2206
+ const { localization, localizeErrors } = useAuthLocalization();
2207
+ const [step, setStep] = useState(1);
2208
+ const [email, setEmail] = useState("");
2209
+ const [phoneNumber, setPhoneNumber] = useState("");
2210
+ const [selectedMethod, setSelectedMethod] = useState(null);
2211
+ const [verificationToken, setVerificationToken] = useState(null);
2212
+ const [isSubmitting, setIsSubmitting] = useState(false);
2213
+ const [formError, setFormError] = useState(null);
2214
+ const [cooldownRemaining, setCooldownRemaining] = useState(0);
2215
+ const codeRef = useRef("");
2216
+ const emailForm = useForm({
2217
+ resolver: zodResolver(recoverEmailSchema),
2218
+ defaultValues: { email: "" }
2219
+ });
2220
+ const phoneForm = useForm({
2221
+ resolver: zodResolver(recoverPhoneSchema),
2222
+ defaultValues: { phoneNumber: "" }
2223
+ });
2224
+ const passwordForm = useForm({
2225
+ resolver: zodResolver(recoverNewPasswordSchema),
2226
+ defaultValues: { newPassword: "", confirmPassword: "" }
2227
+ });
2228
+ useEffect(() => {
2229
+ if (cooldownRemaining <= 0) return;
2230
+ const timer = setInterval(() => {
2231
+ setCooldownRemaining((prev) => Math.max(0, prev - 1));
2232
+ }, 1e3);
2233
+ return () => clearInterval(timer);
2234
+ }, [cooldownRemaining]);
2235
+ const getTotalSteps = useCallback(() => {
2236
+ if (selectedMethod === "email_link") return 3;
2237
+ return 4;
2238
+ }, [selectedMethod]);
2239
+ const handleGoBack = useCallback(() => {
2240
+ if (step === 2) {
2241
+ setStep(1);
2242
+ setSelectedMethod(null);
2243
+ } else if (step === 3) {
2244
+ setStep(2);
2245
+ codeRef.current = "";
2246
+ } else if (step === 4) {
2247
+ setStep(3);
2248
+ }
2249
+ setFormError(null);
2250
+ }, [step]);
2251
+ const onEmailSubmit = useCallback(async (data) => {
2252
+ setFormError(null);
2253
+ setEmail(data.email);
2254
+ setStep(2);
2255
+ }, []);
2256
+ const handleMethodSelect = useCallback(
2257
+ async (method) => {
2258
+ setFormError(null);
2259
+ setIsSubmitting(true);
2260
+ setSelectedMethod(method);
2261
+ try {
2262
+ if (method === "email_link") {
2263
+ await requestPasswordReset({ email, redirectUrl });
2264
+ setCooldownRemaining(resendCooldown);
2265
+ setStep(3);
2266
+ } else if (method === "email_code") {
2267
+ await sendVerificationEmail({ email, redirectUrl });
2268
+ setCooldownRemaining(resendCooldown);
2269
+ setStep(3);
2270
+ } else if (method === "phone_otp") {
2271
+ setStep(3);
2272
+ }
2273
+ } catch (err) {
2274
+ setFormError(getLocalizedError(err, localization, localizeErrors));
2275
+ onError?.(toAuthError(err));
2276
+ } finally {
2277
+ setIsSubmitting(false);
2278
+ }
2279
+ },
2280
+ [
2281
+ email,
2282
+ redirectUrl,
2283
+ resendCooldown,
2284
+ requestPasswordReset,
2285
+ sendVerificationEmail,
2286
+ onError,
2287
+ localization,
2288
+ localizeErrors
2289
+ ]
2290
+ );
2291
+ const handleResendEmailLink = useCallback(async () => {
2292
+ setFormError(null);
2293
+ setIsSubmitting(true);
2294
+ try {
2295
+ await requestPasswordReset({ email, redirectUrl });
2296
+ setCooldownRemaining(resendCooldown);
2297
+ } catch (err) {
2298
+ setFormError(getLocalizedError(err, localization, localizeErrors));
2299
+ } finally {
2300
+ setIsSubmitting(false);
2301
+ }
2302
+ }, [email, redirectUrl, resendCooldown, requestPasswordReset, localization, localizeErrors]);
2303
+ const handleResendEmailCode = useCallback(async () => {
2304
+ setFormError(null);
2305
+ setIsSubmitting(true);
2306
+ try {
2307
+ await sendVerificationEmail({ email, redirectUrl });
2308
+ setCooldownRemaining(resendCooldown);
2309
+ } catch (err) {
2310
+ setFormError(getLocalizedError(err, localization, localizeErrors));
2311
+ } finally {
2312
+ setIsSubmitting(false);
2313
+ }
2314
+ }, [email, redirectUrl, resendCooldown, sendVerificationEmail, localization, localizeErrors]);
2315
+ const handleResendPhoneOTP = useCallback(async () => {
2316
+ setFormError(null);
2317
+ setIsSubmitting(true);
2318
+ try {
2319
+ await sendOTP(phoneNumber);
2320
+ setCooldownRemaining(resendCooldown);
2321
+ } catch (err) {
2322
+ setFormError(getLocalizedError(err, localization, localizeErrors));
2323
+ } finally {
2324
+ setIsSubmitting(false);
2325
+ }
2326
+ }, [phoneNumber, resendCooldown, sendOTP, localization, localizeErrors]);
2327
+ const onPhoneSubmit = useCallback(
2328
+ async (data) => {
2329
+ setFormError(null);
2330
+ setIsSubmitting(true);
2331
+ try {
2332
+ setPhoneNumber(data.phoneNumber);
2333
+ await sendOTP(data.phoneNumber);
2334
+ setCooldownRemaining(resendCooldown);
2335
+ setFormError(null);
2336
+ } catch (err) {
2337
+ setFormError(getLocalizedError(err, localization, localizeErrors));
2338
+ onError?.(toAuthError(err));
2339
+ } finally {
2340
+ setIsSubmitting(false);
2341
+ }
2342
+ },
2343
+ [sendOTP, resendCooldown, onError, localization, localizeErrors]
2344
+ );
2345
+ const handleVerifyEmailCode = useCallback(
2346
+ async (code) => {
2347
+ setFormError(null);
2348
+ setIsSubmitting(true);
2349
+ try {
2350
+ await verifyEmail({ token: code });
2351
+ setVerificationToken(code);
2352
+ setStep(4);
2353
+ } catch (err) {
2354
+ setFormError(getLocalizedError(err, localization, localizeErrors));
2355
+ onError?.(toAuthError(err));
2356
+ } finally {
2357
+ setIsSubmitting(false);
2358
+ }
2359
+ },
2360
+ [verifyEmail, onError, localization, localizeErrors]
2361
+ );
2362
+ const handleVerifyPhoneCode = useCallback(
2363
+ async (code) => {
2364
+ setFormError(null);
2365
+ setIsSubmitting(true);
2366
+ try {
2367
+ await verifyPhone(phoneNumber, code);
2368
+ setVerificationToken(code);
2369
+ setStep(4);
2370
+ } catch (err) {
2371
+ setFormError(getLocalizedError(err, localization, localizeErrors));
2372
+ onError?.(toAuthError(err));
2373
+ } finally {
2374
+ setIsSubmitting(false);
2375
+ }
2376
+ },
2377
+ [verifyPhone, phoneNumber, onError, localization, localizeErrors]
2378
+ );
2379
+ const handleOtpComplete = useCallback(
2380
+ (code) => {
2381
+ codeRef.current = code;
2382
+ if (selectedMethod === "email_code") {
2383
+ handleVerifyEmailCode(code);
2384
+ } else if (selectedMethod === "phone_otp") {
2385
+ handleVerifyPhoneCode(code);
2386
+ }
2387
+ },
2388
+ [selectedMethod, handleVerifyEmailCode, handleVerifyPhoneCode]
2389
+ );
2390
+ const handleManualVerify = useCallback(
2391
+ (e) => {
2392
+ e.preventDefault();
2393
+ if (codeRef.current) {
2394
+ if (selectedMethod === "email_code") {
2395
+ handleVerifyEmailCode(codeRef.current);
2396
+ } else if (selectedMethod === "phone_otp") {
2397
+ handleVerifyPhoneCode(codeRef.current);
2398
+ }
2399
+ }
2400
+ },
2401
+ [selectedMethod, handleVerifyEmailCode, handleVerifyPhoneCode]
2402
+ );
2403
+ const onPasswordSubmit = useCallback(
2404
+ async (data) => {
2405
+ setFormError(null);
2406
+ setIsSubmitting(true);
2407
+ try {
2408
+ const token = verificationToken ?? "";
2409
+ await resetPassword({ token, newPassword: data.newPassword });
2410
+ onSuccess?.();
2411
+ } catch (err) {
2412
+ setFormError(getLocalizedError(err, localization, localizeErrors));
2413
+ onError?.(toAuthError(err));
2414
+ } finally {
2415
+ setIsSubmitting(false);
2416
+ }
2417
+ },
2418
+ [verificationToken, resetPassword, onSuccess, onError, localization, localizeErrors]
2419
+ );
2420
+ if (step === 1) {
2421
+ return /* @__PURE__ */ jsxs(Card, { className, children: [
2422
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
2423
+ /* @__PURE__ */ jsx(CardTitle, { children: localization.RECOVER_ACCOUNT }),
2424
+ /* @__PURE__ */ jsx(CardDescription, { children: localization.RECOVER_ACCOUNT_DESCRIPTION })
2425
+ ] }),
2426
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs("form", { onSubmit: emailForm.handleSubmit(onEmailSubmit), className: "space-y-4", children: [
2427
+ /* @__PURE__ */ jsx(
2428
+ FormInput,
2429
+ {
2430
+ label: localization.EMAIL,
2431
+ id: "recover-email",
2432
+ type: "email",
2433
+ autoComplete: "email",
2434
+ error: emailForm.formState.errors.email?.message,
2435
+ disabled: isSubmitting,
2436
+ ...emailForm.register("email")
2437
+ }
2438
+ ),
2439
+ formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
2440
+ /* @__PURE__ */ jsx(
2441
+ SubmitButton,
2442
+ {
2443
+ loading: isSubmitting,
2444
+ loadingLabel: localization.LOADING,
2445
+ actionLabel: localization.CONTINUE
2446
+ }
2447
+ )
2448
+ ] }) })
2449
+ ] });
2450
+ }
2451
+ if (step === 2) {
2452
+ return /* @__PURE__ */ jsxs(Card, { className, children: [
2453
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
2454
+ /* @__PURE__ */ jsx(CardTitle, { children: localization.SELECT_RECOVERY_METHOD }),
2455
+ /* @__PURE__ */ jsx(CardDescription, { children: localization.STEP_OF.replace("{{current}}", "2").replace(
2456
+ "{{total}}",
2457
+ String(getTotalSteps())
2458
+ ) })
2459
+ ] }),
2460
+ /* @__PURE__ */ jsxs(CardContent, { className: "space-y-4", children: [
2461
+ /* @__PURE__ */ jsx(
2462
+ "button",
2463
+ {
2464
+ type: "button",
2465
+ onClick: () => handleMethodSelect("email_link"),
2466
+ disabled: isSubmitting,
2467
+ className: "w-full p-4 text-left border rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors",
2468
+ children: /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2469
+ /* @__PURE__ */ jsx("div", { className: "font-medium", children: localization.RESET_VIA_EMAIL }),
2470
+ /* @__PURE__ */ jsx("div", { className: "text-sm text-gray-500", children: localization.FORGOT_PASSWORD_EMAIL })
2471
+ ] })
2472
+ }
2473
+ ),
2474
+ /* @__PURE__ */ jsx(
2475
+ "button",
2476
+ {
2477
+ type: "button",
2478
+ onClick: () => handleMethodSelect("email_code"),
2479
+ disabled: isSubmitting,
2480
+ className: "w-full p-4 text-left border rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors",
2481
+ children: /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2482
+ /* @__PURE__ */ jsx("div", { className: "font-medium", children: localization.VERIFY_WITH_CODE }),
2483
+ /* @__PURE__ */ jsx("div", { className: "text-sm text-gray-500", children: localization.EMAIL_OTP_DESCRIPTION })
2484
+ ] })
2485
+ }
2486
+ ),
2487
+ enablePhone && /* @__PURE__ */ jsx(
2488
+ "button",
2489
+ {
2490
+ type: "button",
2491
+ onClick: () => handleMethodSelect("phone_otp"),
2492
+ disabled: isSubmitting,
2493
+ className: "w-full p-4 text-left border rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors",
2494
+ children: /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2495
+ /* @__PURE__ */ jsx("div", { className: "font-medium", children: localization.VERIFY_VIA_PHONE }),
2496
+ /* @__PURE__ */ jsx("div", { className: "text-sm text-gray-500", children: localization.SENDING_VERIFICATION_CODE })
2497
+ ] })
2498
+ }
2499
+ ),
2500
+ formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
2501
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: handleGoBack, className: "w-full", children: localization.GO_BACK })
2502
+ ] })
2503
+ ] });
2504
+ }
2505
+ if (step === 3) {
2506
+ if (selectedMethod === "email_link") {
2507
+ return /* @__PURE__ */ jsxs(Card, { className, children: [
2508
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
2509
+ /* @__PURE__ */ jsx(CardTitle, { children: localization.CHECK_YOUR_EMAIL }),
2510
+ /* @__PURE__ */ jsx(CardDescription, { children: localization.STEP_OF.replace("{{current}}", "3").replace(
2511
+ "{{total}}",
2512
+ String(getTotalSteps())
2513
+ ) })
2514
+ ] }),
2515
+ /* @__PURE__ */ jsxs(CardContent, { className: "space-y-4", children: [
2516
+ /* @__PURE__ */ jsxs("div", { className: "text-center space-y-2", children: [
2517
+ /* @__PURE__ */ jsx("p", { className: "text-sm", children: localization.FORGOT_PASSWORD_EMAIL }),
2518
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: maskEmail(email) })
2519
+ ] }),
2520
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2521
+ /* @__PURE__ */ jsx(
2522
+ Button,
2523
+ {
2524
+ type: "button",
2525
+ variant: "outline",
2526
+ disabled: isSubmitting || cooldownRemaining > 0,
2527
+ onClick: handleResendEmailLink,
2528
+ className: "w-full",
2529
+ children: cooldownRemaining > 0 ? localization.RESEND_IN.replace("{{seconds}}", String(cooldownRemaining)) : isSubmitting ? localization.RESENDING : localization.RESEND
2530
+ }
2531
+ ),
2532
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: handleGoBack, className: "w-full", children: localization.GO_BACK })
2533
+ ] }),
2534
+ formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600 text-center", children: formError })
2535
+ ] })
2536
+ ] });
2537
+ }
2538
+ if (selectedMethod === "email_code") {
2539
+ return /* @__PURE__ */ jsxs(Card, { className, children: [
2540
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
2541
+ /* @__PURE__ */ jsx(CardTitle, { children: localization.EMAIL_OTP }),
2542
+ /* @__PURE__ */ jsx(CardDescription, { children: localization.STEP_OF.replace("{{current}}", "3").replace(
2543
+ "{{total}}",
2544
+ String(getTotalSteps())
2545
+ ) })
2546
+ ] }),
2547
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
2548
+ /* @__PURE__ */ jsx("div", { className: "text-center space-y-2", children: /* @__PURE__ */ jsxs("p", { className: "text-sm", children: [
2549
+ localization.EMAIL_OTP_SENT,
2550
+ " ",
2551
+ /* @__PURE__ */ jsx("strong", { children: maskEmail(email) })
2552
+ ] }) }),
2553
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleManualVerify, className: "space-y-4", children: [
2554
+ /* @__PURE__ */ jsxs("label", { className: "block text-sm font-medium", children: [
2555
+ localization.ENTER_VERIFICATION_CODE,
2556
+ /* @__PURE__ */ jsx(
2557
+ OTPInput,
2558
+ {
2559
+ length: 6,
2560
+ onComplete: handleOtpComplete,
2561
+ disabled: isSubmitting,
2562
+ className: "mt-2"
2563
+ }
2564
+ )
2565
+ ] }),
2566
+ formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
2567
+ /* @__PURE__ */ jsx(
2568
+ SubmitButton,
2569
+ {
2570
+ loading: isSubmitting,
2571
+ loadingLabel: localization.VERIFYING,
2572
+ actionLabel: localization.VERIFY
2573
+ }
2574
+ ),
2575
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2576
+ /* @__PURE__ */ jsx(
2577
+ Button,
2578
+ {
2579
+ type: "button",
2580
+ variant: "outline",
2581
+ disabled: isSubmitting || cooldownRemaining > 0,
2582
+ onClick: handleResendEmailCode,
2583
+ className: "w-full",
2584
+ children: cooldownRemaining > 0 ? localization.RESEND_IN.replace("{{seconds}}", String(cooldownRemaining)) : isSubmitting ? localization.RESENDING : localization.RESEND
2585
+ }
2586
+ ),
2587
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: handleGoBack, className: "w-full", children: localization.GO_BACK })
2588
+ ] })
2589
+ ] })
2590
+ ] }) })
2591
+ ] });
2592
+ }
2593
+ if (selectedMethod === "phone_otp") {
2594
+ if (!phoneNumber) {
2595
+ return /* @__PURE__ */ jsxs(Card, { className, children: [
2596
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
2597
+ /* @__PURE__ */ jsx(CardTitle, { children: localization.VERIFY_VIA_PHONE }),
2598
+ /* @__PURE__ */ jsx(CardDescription, { children: localization.STEP_OF.replace("{{current}}", "3").replace(
2599
+ "{{total}}",
2600
+ String(getTotalSteps())
2601
+ ) })
2602
+ ] }),
2603
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs("form", { onSubmit: phoneForm.handleSubmit(onPhoneSubmit), className: "space-y-4", children: [
2604
+ /* @__PURE__ */ jsx(
2605
+ FormInput,
2606
+ {
2607
+ label: localization.PHONE_NUMBER_PLACEHOLDER,
2608
+ id: "recover-phone",
2609
+ type: "tel",
2610
+ autoComplete: "tel",
2611
+ error: phoneForm.formState.errors.phoneNumber?.message,
2612
+ disabled: isSubmitting,
2613
+ ...phoneForm.register("phoneNumber")
2614
+ }
2615
+ ),
2616
+ formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
2617
+ /* @__PURE__ */ jsx(
2618
+ SubmitButton,
2619
+ {
2620
+ loading: isSubmitting,
2621
+ loadingLabel: localization.SENDING_VERIFICATION_CODE,
2622
+ actionLabel: localization.SEND_VERIFICATION_CODE
2623
+ }
2624
+ ),
2625
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: handleGoBack, className: "w-full", children: localization.GO_BACK })
2626
+ ] }) })
2627
+ ] });
2628
+ }
2629
+ return /* @__PURE__ */ jsxs(Card, { className, children: [
2630
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
2631
+ /* @__PURE__ */ jsx(CardTitle, { children: localization.ONE_TIME_PASSWORD }),
2632
+ /* @__PURE__ */ jsx(CardDescription, { children: localization.STEP_OF.replace("{{current}}", "3").replace(
2633
+ "{{total}}",
2634
+ String(getTotalSteps())
2635
+ ) })
2636
+ ] }),
2637
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
2638
+ /* @__PURE__ */ jsx("div", { className: "text-center space-y-2", children: /* @__PURE__ */ jsxs("p", { className: "text-sm", children: [
2639
+ localization.EMAIL_OTP_SENT,
2640
+ " ",
2641
+ /* @__PURE__ */ jsx("strong", { children: phoneNumber })
2642
+ ] }) }),
2643
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleManualVerify, className: "space-y-4", children: [
2644
+ /* @__PURE__ */ jsxs("label", { className: "block text-sm font-medium", children: [
2645
+ localization.ENTER_VERIFICATION_CODE,
2646
+ /* @__PURE__ */ jsx(
2647
+ OTPInput,
2648
+ {
2649
+ length: 6,
2650
+ onComplete: handleOtpComplete,
2651
+ disabled: isSubmitting,
2652
+ className: "mt-2"
2653
+ }
2654
+ )
2655
+ ] }),
2656
+ formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
2657
+ /* @__PURE__ */ jsx(
2658
+ SubmitButton,
2659
+ {
2660
+ loading: isSubmitting,
2661
+ loadingLabel: localization.VERIFYING,
2662
+ actionLabel: localization.VERIFY
2663
+ }
2664
+ ),
2665
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2666
+ /* @__PURE__ */ jsx(
2667
+ Button,
2668
+ {
2669
+ type: "button",
2670
+ variant: "outline",
2671
+ disabled: isSubmitting || cooldownRemaining > 0,
2672
+ onClick: handleResendPhoneOTP,
2673
+ className: "w-full",
2674
+ children: cooldownRemaining > 0 ? localization.RESEND_IN.replace("{{seconds}}", String(cooldownRemaining)) : isSubmitting ? localization.RESENDING : localization.RESEND
2675
+ }
2676
+ ),
2677
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: handleGoBack, className: "w-full", children: localization.GO_BACK })
2678
+ ] })
2679
+ ] })
2680
+ ] }) })
2681
+ ] });
2682
+ }
2683
+ }
2684
+ if (step === 4) {
2685
+ return /* @__PURE__ */ jsxs(Card, { className, children: [
2686
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
2687
+ /* @__PURE__ */ jsx(CardTitle, { children: localization.RESET_PASSWORD }),
2688
+ /* @__PURE__ */ jsx(CardDescription, { children: localization.STEP_OF.replace("{{current}}", "4").replace(
2689
+ "{{total}}",
2690
+ String(getTotalSteps())
2691
+ ) })
2692
+ ] }),
2693
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs("form", { onSubmit: passwordForm.handleSubmit(onPasswordSubmit), className: "space-y-4", children: [
2694
+ /* @__PURE__ */ jsx(
2695
+ FormPasswordInput,
2696
+ {
2697
+ label: localization.NEW_PASSWORD,
2698
+ id: "new-password",
2699
+ autoComplete: "new-password",
2700
+ error: passwordForm.formState.errors.newPassword?.message,
2701
+ disabled: isSubmitting,
2702
+ ...passwordForm.register("newPassword")
2703
+ }
2704
+ ),
2705
+ /* @__PURE__ */ jsx(
2706
+ FormPasswordInput,
2707
+ {
2708
+ label: localization.CONFIRM_NEW_PASSWORD,
2709
+ id: "confirm-password",
2710
+ autoComplete: "new-password",
2711
+ error: passwordForm.formState.errors.confirmPassword?.message,
2712
+ disabled: isSubmitting,
2713
+ ...passwordForm.register("confirmPassword")
2714
+ }
2715
+ ),
2716
+ formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
2717
+ /* @__PURE__ */ jsx(
2718
+ SubmitButton,
2719
+ {
2720
+ loading: isSubmitting,
2721
+ loadingLabel: localization.LOADING,
2722
+ actionLabel: localization.RESET_PASSWORD_ACTION
2723
+ }
2724
+ ),
2725
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: handleGoBack, className: "w-full", children: localization.GO_BACK })
2726
+ ] }) })
2727
+ ] });
2728
+ }
2729
+ return null;
2730
+ }
2731
+ var resetPasswordSchema = z.object({
2732
+ newPassword: z.string().min(8),
2733
+ confirmPassword: z.string().min(1)
2734
+ }).refine((data) => data.newPassword === data.confirmPassword, {
2735
+ message: "PASSWORDS_DO_NOT_MATCH",
2736
+ path: ["confirmPassword"]
2737
+ });
2738
+ function ResetPasswordForm({
2739
+ token,
2740
+ onSuccess,
2741
+ onError,
2742
+ className
2743
+ }) {
2744
+ const { resetPassword } = useAuth();
2745
+ const { localization, localizeErrors } = useAuthLocalization();
2746
+ const [isSuccess, setIsSuccess] = useState(false);
2747
+ const [isSubmitting, setIsSubmitting] = useState(false);
2748
+ const [formError, setFormError] = useState(null);
2749
+ const {
2750
+ register,
2751
+ handleSubmit,
2752
+ formState: { errors }
2753
+ } = useForm({
2754
+ resolver: zodResolver(resetPasswordSchema),
2755
+ defaultValues: { newPassword: "", confirmPassword: "" }
2756
+ });
2757
+ const onSubmit = useCallback(
2758
+ async (data) => {
2759
+ setFormError(null);
2760
+ setIsSubmitting(true);
2761
+ try {
2762
+ await resetPassword({ token, newPassword: data.newPassword });
2763
+ setIsSuccess(true);
2764
+ onSuccess?.();
2765
+ } catch (err) {
2766
+ setFormError(getLocalizedError(err, localization, localizeErrors));
2767
+ onError?.(toAuthError(err));
2768
+ } finally {
2769
+ setIsSubmitting(false);
2770
+ }
2771
+ },
2772
+ [resetPassword, token, onSuccess, onError, localization, localizeErrors]
2773
+ );
2774
+ if (isSuccess) {
2775
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-4 text-center", children: [
2776
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: localization.RESET_PASSWORD_SUCCESS }),
2777
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: localization.RESET_PASSWORD_SUCCESS })
2778
+ ] }) });
2779
+ }
2780
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-4", children: [
2781
+ /* @__PURE__ */ jsxs("div", { className: "text-center space-y-2", children: [
2782
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: localization.RESET_PASSWORD }),
2783
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: localization.CHANGE_PASSWORD_DESCRIPTION })
2784
+ ] }),
2785
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit(onSubmit), className: "space-y-4", children: [
2786
+ /* @__PURE__ */ jsx(
2787
+ FormPasswordInput,
2788
+ {
2789
+ label: localization.NEW_PASSWORD,
2790
+ id: "reset-password-new",
2791
+ autoComplete: "new-password",
2792
+ error: errors.newPassword?.message,
2793
+ disabled: isSubmitting,
2794
+ ...register("newPassword")
2795
+ }
2796
+ ),
2797
+ /* @__PURE__ */ jsx(
2798
+ FormPasswordInput,
2799
+ {
2800
+ label: localization.CONFIRM_NEW_PASSWORD,
2801
+ id: "reset-password-confirm",
2802
+ autoComplete: "new-password",
2803
+ error: errors.confirmPassword?.message === "PASSWORDS_DO_NOT_MATCH" ? localization.PASSWORDS_DO_NOT_MATCH || "Passwords do not match" : errors.confirmPassword?.message,
2804
+ disabled: isSubmitting,
2805
+ ...register("confirmPassword")
2806
+ }
2807
+ ),
2808
+ formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
2809
+ /* @__PURE__ */ jsx(
2810
+ SubmitButton,
2811
+ {
2812
+ loading: isSubmitting,
2813
+ loadingLabel: localization.LOADING,
2814
+ actionLabel: localization.RESET_PASSWORD_ACTION
2815
+ }
2816
+ )
2817
+ ] })
2818
+ ] }) });
2819
+ }
2820
+ var signupSchema = z.object({
2821
+ email: z.string().email("Invalid email address"),
2822
+ password: z.string().min(8, "Password must be at least 8 characters"),
2823
+ name: z.string().min(1, "Name is required"),
2824
+ username: z.string().optional(),
2825
+ phoneNumber: z.string().optional(),
2826
+ organizationName: z.string().optional(),
2827
+ acceptTerms: z.boolean(),
2828
+ captchaToken: z.string().optional()
2829
+ });
2830
+ function SignupForm({
2831
+ onSuccess,
2832
+ onError,
2833
+ showUsernameField = false,
2834
+ showPhoneField = false,
2835
+ organizationName,
2836
+ acceptTermsRequired = false,
2837
+ onLoginClick,
2838
+ className
2839
+ }) {
2840
+ const { signup } = useAuth();
2841
+ const { localization, localizeErrors } = useAuthLocalization();
2842
+ const [formError, setFormError] = useState(null);
2843
+ const [isSubmitting, setIsSubmitting] = useState(false);
2844
+ const {
2845
+ register,
2846
+ handleSubmit,
2847
+ formState: { errors }
2848
+ } = useForm({
2849
+ resolver: zodResolver(
2850
+ acceptTermsRequired ? signupSchema.refine((data) => data.acceptTerms === true, {
2851
+ message: localization.ACCEPT_TERMS_ERROR,
2852
+ path: ["acceptTerms"]
2853
+ }) : signupSchema
2854
+ ),
2855
+ defaultValues: {
2856
+ email: "",
2857
+ password: "",
2858
+ name: "",
2859
+ username: "",
2860
+ phoneNumber: "",
2861
+ organizationName: organizationName ?? "",
2862
+ acceptTerms: false
2863
+ }
2864
+ });
2865
+ const onSubmit = useCallback(
2866
+ async (data) => {
2867
+ setFormError(null);
2868
+ setIsSubmitting(true);
2869
+ try {
2870
+ const response = await signup({
2871
+ email: data.email,
2872
+ password: data.password,
2873
+ name: data.name,
2874
+ username: data.username,
2875
+ organizationName: data.organizationName,
2876
+ acceptTerms: data.acceptTerms
2877
+ });
2878
+ onSuccess(response.user);
2879
+ } catch (err) {
2880
+ setFormError(getLocalizedError(err, localization, localizeErrors));
2881
+ onError?.(toAuthError(err));
2882
+ } finally {
2883
+ setIsSubmitting(false);
2884
+ }
2885
+ },
2886
+ [signup, onSuccess, onError, localization, localizeErrors]
2887
+ );
2888
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit(onSubmit), className: "p-6 space-y-4", children: [
2889
+ /* @__PURE__ */ jsx(
2890
+ FormInput,
2891
+ {
2892
+ label: localization.NAME,
2893
+ id: "signup-name",
2894
+ type: "text",
2895
+ autoComplete: "name",
2896
+ error: errors.name?.message,
2897
+ ...register("name")
2898
+ }
2899
+ ),
2900
+ /* @__PURE__ */ jsx(
2901
+ FormInput,
2902
+ {
2903
+ label: localization.EMAIL,
2904
+ id: "signup-email",
2905
+ type: "email",
2906
+ autoComplete: "email",
2907
+ error: errors.email?.message,
2908
+ ...register("email")
2909
+ }
2910
+ ),
2911
+ /* @__PURE__ */ jsx(
2912
+ FormPasswordInput,
2913
+ {
2914
+ label: localization.PASSWORD,
2915
+ id: "signup-password",
2916
+ autoComplete: "new-password",
2917
+ error: errors.password?.message,
2918
+ ...register("password")
2919
+ }
2920
+ ),
2921
+ showUsernameField && /* @__PURE__ */ jsx(
2922
+ FormInput,
2923
+ {
2924
+ label: localization.USERNAME,
2925
+ id: "signup-username",
2926
+ type: "text",
2927
+ autoComplete: "username",
2928
+ error: errors.username?.message,
2929
+ ...register("username")
2930
+ }
2931
+ ),
2932
+ showPhoneField && /* @__PURE__ */ jsx(
2933
+ FormInput,
2934
+ {
2935
+ label: localization.PHONE_NUMBER_PLACEHOLDER,
2936
+ id: "signup-phone",
2937
+ type: "tel",
2938
+ autoComplete: "tel",
2939
+ error: errors.phoneNumber?.message,
2940
+ ...register("phoneNumber")
2941
+ }
2942
+ ),
2943
+ acceptTermsRequired && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2944
+ /* @__PURE__ */ jsx(
2945
+ "input",
2946
+ {
2947
+ id: "signup-terms",
2948
+ type: "checkbox",
2949
+ "aria-invalid": errors.acceptTerms ? true : void 0,
2950
+ ...register("acceptTerms")
2951
+ }
2952
+ ),
2953
+ /* @__PURE__ */ jsx("label", { htmlFor: "signup-terms", className: "text-sm", children: localization.ACCEPT_TERMS }),
2954
+ errors.acceptTerms && /* @__PURE__ */ jsx("p", { className: "text-sm text-red-600", children: errors.acceptTerms.message })
2955
+ ] }),
2956
+ formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
2957
+ /* @__PURE__ */ jsx(
2958
+ SubmitButton,
2959
+ {
2960
+ loading: isSubmitting,
2961
+ loadingLabel: localization.SIGNING_IN,
2962
+ actionLabel: localization.SIGN_UP_ACTION
2963
+ }
2964
+ ),
2965
+ onLoginClick && /* @__PURE__ */ jsxs("p", { className: "text-sm text-center", children: [
2966
+ localization.ALREADY_HAVE_AN_ACCOUNT,
2967
+ " ",
2968
+ /* @__PURE__ */ jsx(
2969
+ Button,
2970
+ {
2971
+ variant: "link",
2972
+ type: "button",
2973
+ className: "p-0 h-auto text-sm",
2974
+ onClick: onLoginClick,
2975
+ children: localization.SIGN_IN
2976
+ }
2977
+ )
2978
+ ] })
2979
+ ] }) });
2980
+ }
2981
+ var totpSchema = z.object({
2982
+ code: z.string().length(6).regex(/^\d{6}$/)
2983
+ });
2984
+ var backupCodeSchema = z.object({
2985
+ backupCode: z.string().min(1)
2986
+ });
2987
+ function TwoFactorForm({
2988
+ onVerify,
2989
+ onError,
2990
+ autoSubmit = true,
2991
+ isLoading,
2992
+ className
2993
+ }) {
2994
+ const { localization, localizeErrors } = useAuthLocalization();
2995
+ const [mode, setMode] = useState("totp");
2996
+ const [isSubmitting, setIsSubmitting] = useState(false);
2997
+ const [formError, setFormError] = useState(null);
2998
+ const codeRef = useRef("");
2999
+ const loading = isLoading ?? isSubmitting;
3000
+ const backupForm = useForm({
3001
+ resolver: zodResolver(backupCodeSchema),
3002
+ defaultValues: { backupCode: "" }
3003
+ });
3004
+ const handleVerify = useCallback(
3005
+ async (code) => {
3006
+ setIsSubmitting(true);
3007
+ setFormError(null);
3008
+ try {
3009
+ await onVerify(code);
3010
+ } catch (err) {
3011
+ setFormError(getLocalizedError(err, localization, localizeErrors));
3012
+ onError?.(toAuthError(err));
3013
+ } finally {
3014
+ setIsSubmitting(false);
3015
+ }
3016
+ },
3017
+ [onVerify, onError, localization, localizeErrors]
3018
+ );
3019
+ const handleOtpComplete = useCallback(
3020
+ (code) => {
3021
+ codeRef.current = code;
3022
+ if (autoSubmit) {
3023
+ handleVerify(code);
3024
+ }
3025
+ },
3026
+ [autoSubmit, handleVerify]
3027
+ );
3028
+ const handleManualVerify = useCallback(
3029
+ (e) => {
3030
+ e.preventDefault();
3031
+ if (codeRef.current) {
3032
+ handleVerify(codeRef.current);
3033
+ }
3034
+ },
3035
+ [handleVerify]
3036
+ );
3037
+ const handleBackupSubmit = useCallback(
3038
+ async (data) => {
3039
+ await handleVerify(data.backupCode);
3040
+ },
3041
+ [handleVerify]
3042
+ );
3043
+ return /* @__PURE__ */ jsxs(Card, { className, children: [
3044
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
3045
+ /* @__PURE__ */ jsx(CardTitle, { children: localization.TWO_FACTOR_PROMPT }),
3046
+ /* @__PURE__ */ jsx(CardDescription, { children: mode === "totp" ? localization.ENTER_TOTP_CODE : localization.BACKUP_CODES })
3047
+ ] }),
3048
+ /* @__PURE__ */ jsxs(CardContent, { className: "space-y-4", children: [
3049
+ mode === "totp" && /* @__PURE__ */ jsxs("form", { onSubmit: handleManualVerify, className: "space-y-4", children: [
3050
+ /* @__PURE__ */ jsxs("label", { className: "block text-sm font-medium", children: [
3051
+ localization.VERIFICATION_CODE_LABEL,
3052
+ /* @__PURE__ */ jsx(
3053
+ OTPInput,
3054
+ {
3055
+ length: 6,
3056
+ onComplete: handleOtpComplete,
3057
+ disabled: loading,
3058
+ className: "mt-2"
3059
+ }
3060
+ )
3061
+ ] }),
3062
+ formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
3063
+ /* @__PURE__ */ jsx(
3064
+ SubmitButton,
3065
+ {
3066
+ loading,
3067
+ loadingLabel: localization.VERIFYING,
3068
+ actionLabel: localization.TWO_FACTOR_ACTION
3069
+ }
3070
+ ),
3071
+ /* @__PURE__ */ jsx(
3072
+ Button,
3073
+ {
3074
+ variant: "link",
3075
+ type: "button",
3076
+ className: "p-0 h-auto text-sm",
3077
+ onClick: () => setMode("backup"),
3078
+ disabled: loading,
3079
+ children: localization.USE_BACKUP_CODE
3080
+ }
3081
+ )
3082
+ ] }),
3083
+ mode === "backup" && /* @__PURE__ */ jsxs("form", { onSubmit: backupForm.handleSubmit(handleBackupSubmit), className: "space-y-4", children: [
3084
+ /* @__PURE__ */ jsx(
3085
+ FormInput,
3086
+ {
3087
+ label: localization.BACKUP_CODE_PLACEHOLDER,
3088
+ id: "backup-code",
3089
+ error: backupForm.formState.errors.backupCode?.message,
3090
+ disabled: loading,
3091
+ ...backupForm.register("backupCode")
3092
+ }
3093
+ ),
3094
+ formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
3095
+ /* @__PURE__ */ jsx(
3096
+ SubmitButton,
3097
+ {
3098
+ loading,
3099
+ loadingLabel: localization.VERIFYING,
3100
+ actionLabel: localization.VERIFY
3101
+ }
3102
+ ),
3103
+ /* @__PURE__ */ jsx(
3104
+ Button,
3105
+ {
3106
+ variant: "link",
3107
+ type: "button",
3108
+ className: "p-0 h-auto text-sm",
3109
+ onClick: () => setMode("totp"),
3110
+ disabled: loading,
3111
+ children: localization.USE_AUTHENTICATOR
3112
+ }
3113
+ )
3114
+ ] })
3115
+ ] })
3116
+ ] });
3117
+ }
3118
+ function AuthView({
3119
+ pathname,
3120
+ paths,
3121
+ fallback = null,
3122
+ searchParams,
3123
+ getSearchParam,
3124
+ onSuccess,
3125
+ onError,
3126
+ enablePhone,
3127
+ oauthProviders,
3128
+ oauthRedirectUri,
3129
+ redirectUrl,
3130
+ className
3131
+ }) {
3132
+ const resolvedPaths = useMemo(() => {
3133
+ return { ...authViewPaths, ...paths };
3134
+ }, [paths]);
3135
+ const getParam = (key) => {
3136
+ if (searchParams) {
3137
+ return searchParams[key];
3138
+ }
3139
+ if (getSearchParam) {
3140
+ return getSearchParam(key);
3141
+ }
3142
+ return void 0;
3143
+ };
3144
+ const matchedPath = Object.entries(resolvedPaths).find(
3145
+ ([, mappedPath]) => mappedPath === pathname
3146
+ )?.[0];
3147
+ switch (matchedPath) {
3148
+ case "/sign-in": {
3149
+ if (oauthProviders && oauthRedirectUri) {
3150
+ return /* @__PURE__ */ jsx(
3151
+ LoginForm,
3152
+ {
3153
+ onSuccess: (user) => onSuccess?.(user),
3154
+ onError,
3155
+ enablePhone,
3156
+ oauthProviders,
3157
+ oauthRedirectUri,
3158
+ className
3159
+ }
3160
+ );
3161
+ }
3162
+ return /* @__PURE__ */ jsx(
3163
+ LoginForm,
3164
+ {
3165
+ onSuccess: (user) => onSuccess?.(user),
3166
+ onError,
3167
+ enablePhone,
3168
+ className
3169
+ }
3170
+ );
3171
+ }
3172
+ case "/sign-up": {
3173
+ return /* @__PURE__ */ jsx(
3174
+ SignupForm,
3175
+ {
3176
+ onSuccess: (user) => onSuccess?.(user),
3177
+ onError,
3178
+ className
3179
+ }
3180
+ );
3181
+ }
3182
+ case "/forgot-password": {
3183
+ if (!redirectUrl) {
3184
+ throw new Error("AuthView: redirectUrl is required for /forgot-password");
3185
+ }
3186
+ return /* @__PURE__ */ jsx(
3187
+ ForgotPasswordForm,
3188
+ {
3189
+ redirectUrl,
3190
+ onSuccess: () => onSuccess?.(),
3191
+ onError,
3192
+ className
3193
+ }
3194
+ );
3195
+ }
3196
+ case "/reset-password": {
3197
+ const token = getParam("token");
3198
+ if (!token) {
3199
+ throw new Error("AuthView: token query param is required for /reset-password");
3200
+ }
3201
+ return /* @__PURE__ */ jsx(
3202
+ ResetPasswordForm,
3203
+ {
3204
+ token,
3205
+ onSuccess: () => onSuccess?.(),
3206
+ onError,
3207
+ className
3208
+ }
3209
+ );
3210
+ }
3211
+ case "/verify-email": {
3212
+ if (!redirectUrl) {
3213
+ throw new Error("AuthView: redirectUrl is required for /verify-email");
3214
+ }
3215
+ const token = getParam("token");
3216
+ const email = getParam("email");
3217
+ return /* @__PURE__ */ jsx(
3218
+ EmailVerificationForm,
3219
+ {
3220
+ token,
3221
+ email,
3222
+ redirectUrl,
3223
+ onSuccess: (user) => onSuccess?.(user),
3224
+ onError,
3225
+ className
3226
+ }
3227
+ );
3228
+ }
3229
+ case "/email-otp": {
3230
+ if (!redirectUrl) {
3231
+ throw new Error("AuthView: redirectUrl is required for /email-otp");
3232
+ }
3233
+ return /* @__PURE__ */ jsx(
3234
+ EmailOTPForm,
3235
+ {
3236
+ redirectUrl,
3237
+ onSuccess: (user) => onSuccess?.(user),
3238
+ onError,
3239
+ className
3240
+ }
3241
+ );
3242
+ }
3243
+ case "/recover-account": {
3244
+ if (!redirectUrl) {
3245
+ throw new Error("AuthView: redirectUrl is required for /recover-account");
3246
+ }
3247
+ return /* @__PURE__ */ jsx(
3248
+ RecoverAccountForm,
3249
+ {
3250
+ redirectUrl,
3251
+ enablePhone,
3252
+ onSuccess: () => onSuccess?.(),
3253
+ onError,
3254
+ className
3255
+ }
3256
+ );
3257
+ }
3258
+ case "/two-factor": {
3259
+ return /* @__PURE__ */ jsx(
3260
+ TwoFactorForm,
3261
+ {
3262
+ onVerify: () => {
3263
+ onSuccess?.();
3264
+ },
3265
+ onError,
3266
+ className
3267
+ }
3268
+ );
3269
+ }
3270
+ default: {
3271
+ return /* @__PURE__ */ jsx(Fragment, { children: fallback });
3272
+ }
3273
+ }
3274
+ }
3275
+ var magicLinkSchema = z.object({
3276
+ email: z.string().email("Invalid email address")
3277
+ });
3278
+ function MagicLinkForm({ redirectUrl, onSuccess, onError, className }) {
3279
+ const { sendMagicLink } = useAuth();
3280
+ const { localization, localizeErrors } = useAuthLocalization();
3281
+ const [sent, setSent] = useState(false);
3282
+ const [sentEmail, setSentEmail] = useState("");
3283
+ const [isSubmitting, setIsSubmitting] = useState(false);
3284
+ const [formError, setFormError] = useState(null);
3285
+ const {
3286
+ register,
3287
+ handleSubmit,
3288
+ formState: { errors },
3289
+ getValues
3290
+ } = useForm({
3291
+ resolver: zodResolver(magicLinkSchema),
3292
+ defaultValues: { email: "" }
3293
+ });
3294
+ const onSubmit = useCallback(
3295
+ async (data) => {
3296
+ setFormError(null);
3297
+ setIsSubmitting(true);
3298
+ try {
3299
+ await sendMagicLink({ email: data.email, redirectUrl });
3300
+ setSent(true);
3301
+ setSentEmail(data.email);
3302
+ onSuccess?.();
3303
+ } catch (err) {
3304
+ setFormError(getLocalizedError(err, localization, localizeErrors));
3305
+ onError?.(toAuthError(err));
3306
+ } finally {
3307
+ setIsSubmitting(false);
3308
+ }
3309
+ },
3310
+ [sendMagicLink, redirectUrl, onSuccess, onError, localization, localizeErrors]
3311
+ );
3312
+ const handleResend = useCallback(async () => {
3313
+ const email = getValues("email");
3314
+ setFormError(null);
3315
+ setIsSubmitting(true);
3316
+ try {
3317
+ await sendMagicLink({ email, redirectUrl });
3318
+ } catch (err) {
3319
+ setFormError(getLocalizedError(err, localization, localizeErrors));
3320
+ } finally {
3321
+ setIsSubmitting(false);
3322
+ }
3323
+ }, [sendMagicLink, redirectUrl, getValues, localization, localizeErrors]);
3324
+ if (sent) {
3325
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-4 text-center", children: [
3326
+ /* @__PURE__ */ jsxs("p", { className: "text-sm", children: [
3327
+ localization.MAGIC_LINK_EMAIL,
3328
+ " ",
3329
+ /* @__PURE__ */ jsx("strong", { children: maskEmail(sentEmail) })
3330
+ ] }),
3331
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: localization.MAGIC_LINK_HINT }),
3332
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", disabled: isSubmitting, onClick: handleResend, children: isSubmitting ? localization.RESENDING : localization.RESEND }),
3333
+ formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError })
3334
+ ] }) });
3335
+ }
3336
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit(onSubmit), className: "p-6 space-y-4", children: [
3337
+ /* @__PURE__ */ jsx(
3338
+ FormInput,
3339
+ {
3340
+ label: localization.EMAIL,
3341
+ id: "magic-link-email",
3342
+ type: "email",
3343
+ autoComplete: "email",
3344
+ error: errors.email?.message,
3345
+ ...register("email")
3346
+ }
3347
+ ),
3348
+ formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
3349
+ /* @__PURE__ */ jsx(
3350
+ SubmitButton,
3351
+ {
3352
+ loading: isSubmitting,
3353
+ loadingLabel: localization.SENDING,
3354
+ actionLabel: localization.MAGIC_LINK_ACTION
3355
+ }
3356
+ )
3357
+ ] }) });
3358
+ }
3359
+ var DEFAULT_DEVICE_ICONS = {
3360
+ mobile: "\u{1F4F1}",
3361
+ desktop: "\u{1F4BB}",
3362
+ tablet: "\u{1F4DF}"
3363
+ };
3364
+ function DeviceList({
3365
+ showTrustToggle = false,
3366
+ showRemoveAction = false,
3367
+ onDeviceTrusted,
3368
+ onDeviceUntrusted,
3369
+ onDeviceRemoved,
3370
+ renderDeviceIcon,
3371
+ className
3372
+ }) {
3373
+ const { devices, isLoading, error, refresh, trustDevice, untrustDevice, removeDevice } = useDevices();
3374
+ const { localization } = useAuthLocalization();
3375
+ useEffect(() => {
3376
+ refresh();
3377
+ }, [refresh]);
3378
+ const handleTrustToggle = useCallback(
3379
+ async (device) => {
3380
+ if (device.isTrusted) {
3381
+ await untrustDevice(device.id);
3382
+ onDeviceUntrusted?.(device);
3383
+ } else {
3384
+ await trustDevice(device.id);
3385
+ onDeviceTrusted?.(device);
3386
+ }
3387
+ },
3388
+ [trustDevice, untrustDevice, onDeviceTrusted, onDeviceUntrusted]
3389
+ );
3390
+ const handleRemove = useCallback(
3391
+ async (device) => {
3392
+ await removeDevice(device.id);
3393
+ onDeviceRemoved?.(device);
3394
+ },
3395
+ [removeDevice, onDeviceRemoved]
3396
+ );
3397
+ if (isLoading && devices.length === 0) {
3398
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsx("div", { className: "p-6 text-center text-sm text-gray-500", children: localization.LOADING_DEVICES }) });
3399
+ }
3400
+ if (error) {
3401
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsx("div", { className: "p-6 text-center text-sm text-red-600", children: error.message }) });
3402
+ }
3403
+ if (devices.length === 0) {
3404
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsx("div", { className: "p-6 text-center text-sm text-gray-500", children: localization.NO_DEVICES }) });
3405
+ }
3406
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsx("div", { className: "p-6 space-y-4", children: devices.map((device) => /* @__PURE__ */ jsxs(
3407
+ "div",
3408
+ {
3409
+ className: "flex items-start justify-between gap-4 rounded border p-4",
3410
+ children: [
3411
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
3412
+ /* @__PURE__ */ jsx("span", { className: "text-2xl", children: renderDeviceIcon ? renderDeviceIcon(device) : DEFAULT_DEVICE_ICONS[device.deviceType] ?? "\u{1F4BB}" }),
3413
+ /* @__PURE__ */ jsxs("div", { children: [
3414
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3415
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: device.name }),
3416
+ device.isCurrent && /* @__PURE__ */ jsx("span", { className: "rounded bg-blue-100 px-2 py-0.5 text-xs text-blue-700", children: localization.CURRENT_DEVICE })
3417
+ ] }),
3418
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-500", children: [
3419
+ device.browser,
3420
+ " \xB7 ",
3421
+ device.os
3422
+ ] }),
3423
+ (device.lastCity || device.lastCountry) && /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: [device.lastCity, device.lastCountry].filter(Boolean).join(", ") }),
3424
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-400", children: [
3425
+ localization.LAST_USED,
3426
+ " ",
3427
+ device.lastUsedAt,
3428
+ " \xB7 ",
3429
+ localization.IP_LABEL,
3430
+ " ",
3431
+ device.lastIp
3432
+ ] })
3433
+ ] })
3434
+ ] }),
3435
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3436
+ showTrustToggle && /* @__PURE__ */ jsx(
3437
+ Button,
3438
+ {
3439
+ type: "button",
3440
+ variant: "outline",
3441
+ size: "sm",
3442
+ onClick: () => handleTrustToggle(device),
3443
+ children: device.isTrusted ? localization.UNTRUST_DEVICE : localization.TRUST_DEVICE
3444
+ }
3445
+ ),
3446
+ showRemoveAction && /* @__PURE__ */ jsx(
3447
+ Button,
3448
+ {
3449
+ type: "button",
3450
+ variant: "destructive",
3451
+ size: "sm",
3452
+ disabled: device.isCurrent,
3453
+ onClick: () => handleRemove(device),
3454
+ children: localization.REMOVE
3455
+ }
3456
+ )
3457
+ ] })
3458
+ ]
3459
+ },
3460
+ device.id
3461
+ )) }) });
3462
+ }
3463
+ function LoadingBoundary({
3464
+ children,
3465
+ spinner = /* @__PURE__ */ jsx("div", { children: "Loading..." }),
3466
+ delay = 0,
3467
+ className
3468
+ }) {
3469
+ const { isLoading } = useSession();
3470
+ const [showSpinner, setShowSpinner] = useState(delay === 0 && isLoading);
3471
+ useEffect(() => {
3472
+ if (!isLoading) {
3473
+ setShowSpinner(false);
3474
+ return;
3475
+ }
3476
+ if (delay === 0) {
3477
+ setShowSpinner(true);
3478
+ return;
3479
+ }
3480
+ const timer = setTimeout(() => {
3481
+ setShowSpinner(true);
3482
+ }, delay);
3483
+ return () => clearTimeout(timer);
3484
+ }, [isLoading, delay]);
3485
+ if (isLoading && showSpinner) {
3486
+ return className ? /* @__PURE__ */ jsx("div", { className, children: spinner }) : /* @__PURE__ */ jsx(Fragment, { children: spinner });
3487
+ }
3488
+ if (isLoading) {
3489
+ return null;
3490
+ }
3491
+ return /* @__PURE__ */ jsx(Fragment, { children });
3492
+ }
3493
+
3494
+ // src/helpers/risk-utils.ts
3495
+ function getRiskLevel(score) {
3496
+ if (score < 30) return "low";
3497
+ if (score < 60) return "medium";
3498
+ return "high";
3499
+ }
3500
+ var RISK_COLORS = {
3501
+ low: "text-green-600 bg-green-50",
3502
+ medium: "text-yellow-600 bg-yellow-50",
3503
+ high: "text-red-600 bg-red-50"
3504
+ };
3505
+ function LoginActivityFeed({
3506
+ limit = 20,
3507
+ showRiskBadge = false,
3508
+ showReportActions = false,
3509
+ filterByStatus,
3510
+ onSuspiciousReported,
3511
+ className
3512
+ }) {
3513
+ const {
3514
+ events,
3515
+ hasMore,
3516
+ isLoading,
3517
+ error,
3518
+ refresh,
3519
+ loadMore,
3520
+ filter,
3521
+ markRecognized,
3522
+ reportSuspicious
3523
+ } = useLoginActivity({ limit });
3524
+ const { localization } = useAuthLocalization();
3525
+ useEffect(() => {
3526
+ if (filterByStatus) {
3527
+ filter({ status: filterByStatus, limit });
3528
+ }
3529
+ refresh();
3530
+ }, [refresh, filter, filterByStatus, limit]);
3531
+ const handleMarkRecognized = useCallback(
3532
+ async (event) => {
3533
+ await markRecognized(event.id);
3534
+ },
3535
+ [markRecognized]
3536
+ );
3537
+ const handleReportSuspicious = useCallback(
3538
+ async (event) => {
3539
+ await reportSuspicious(event.id);
3540
+ onSuspiciousReported?.(event);
3541
+ },
3542
+ [reportSuspicious, onSuspiciousReported]
3543
+ );
3544
+ if (isLoading && events.length === 0) {
3545
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsx("div", { className: "p-6 text-center text-sm text-gray-500", children: localization.LOADING_ACTIVITY }) });
3546
+ }
3547
+ if (error) {
3548
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsx("div", { className: "p-6 text-center text-sm text-red-600", children: error.message }) });
3549
+ }
3550
+ if (events.length === 0) {
3551
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsx("div", { className: "p-6 text-center text-sm text-gray-500", children: localization.NO_ACTIVITY }) });
3552
+ }
3553
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-4", children: [
3554
+ events.map((event) => {
3555
+ const riskLevel = getRiskLevel(event.riskScore);
3556
+ return /* @__PURE__ */ jsxs(
3557
+ "div",
3558
+ {
3559
+ className: "flex items-start justify-between gap-4 rounded border p-4",
3560
+ children: [
3561
+ /* @__PURE__ */ jsxs("div", { children: [
3562
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3563
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: event.deviceName }),
3564
+ /* @__PURE__ */ jsx("span", { className: "rounded bg-gray-100 px-2 py-0.5 text-xs text-gray-600", children: event.status }),
3565
+ showRiskBadge && /* @__PURE__ */ jsx("span", { className: `rounded px-2 py-0.5 text-xs ${RISK_COLORS[riskLevel]}`, children: riskLevel })
3566
+ ] }),
3567
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-500", children: [
3568
+ event.method,
3569
+ " \xB7 ",
3570
+ event.ip
3571
+ ] }),
3572
+ (event.city || event.country) && /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: [event.city, event.country].filter(Boolean).join(", ") }),
3573
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: event.createdAt })
3574
+ ] }),
3575
+ showReportActions && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3576
+ /* @__PURE__ */ jsx(
3577
+ Button,
3578
+ {
3579
+ type: "button",
3580
+ variant: "outline",
3581
+ size: "sm",
3582
+ onClick: () => handleMarkRecognized(event),
3583
+ children: localization.MARK_RECOGNIZED
3584
+ }
3585
+ ),
3586
+ /* @__PURE__ */ jsx(
3587
+ Button,
3588
+ {
3589
+ type: "button",
3590
+ variant: "destructive",
3591
+ size: "sm",
3592
+ onClick: () => handleReportSuspicious(event),
3593
+ children: localization.REPORT_SUSPICIOUS
3594
+ }
3595
+ )
3596
+ ] })
3597
+ ]
3598
+ },
3599
+ event.id
3600
+ );
3601
+ }),
3602
+ hasMore && /* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", disabled: isLoading, onClick: loadMore, children: isLoading ? localization.LOADING : localization.LOAD_MORE }) })
3603
+ ] }) });
3604
+ }
3605
+ function OrganizationMenu({
3606
+ showCreateNew = false,
3607
+ currentOrgId,
3608
+ onSwitch,
3609
+ onCreateNew,
3610
+ className
3611
+ }) {
3612
+ const { organizations, isLoading, refresh } = useOrganizations();
3613
+ const { localization } = useAuthLocalization();
3614
+ useEffect(() => {
3615
+ refresh();
3616
+ }, [refresh]);
3617
+ const currentOrg = organizations.find((o) => o.id === currentOrgId);
3618
+ return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [
3619
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "outline", disabled: isLoading, children: currentOrg ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
3620
+ currentOrg.logo ? /* @__PURE__ */ jsx("img", { src: currentOrg.logo, alt: "", className: "h-5 w-5 rounded" }) : /* @__PURE__ */ jsx("span", { className: "flex h-5 w-5 items-center justify-center rounded bg-gray-200 text-xs", children: getInitials(currentOrg.name) }),
3621
+ currentOrg.name
3622
+ ] }) : localization.SELECT_ORGANIZATION }) }),
3623
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", children: [
3624
+ organizations.map((org) => /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: () => onSwitch?.(org), children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3625
+ org.logo ? /* @__PURE__ */ jsx("img", { src: org.logo, alt: "", className: "h-5 w-5 rounded" }) : /* @__PURE__ */ jsx("span", { className: "flex h-5 w-5 items-center justify-center rounded bg-gray-200 text-xs", children: getInitials(org.name) }),
3626
+ /* @__PURE__ */ jsx("span", { children: org.name }),
3627
+ org.id === currentOrgId && /* @__PURE__ */ jsx("span", { className: "ml-auto", children: "\u2713" })
3628
+ ] }) }, org.id)),
3629
+ showCreateNew && /* @__PURE__ */ jsxs(Fragment, { children: [
3630
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
3631
+ /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: onCreateNew, children: localization.CREATE_ORGANIZATION })
3632
+ ] })
3633
+ ] })
3634
+ ] }) });
3635
+ }
3636
+ function UserMenu({ showOrganization = false, menuItems, className }) {
3637
+ const { user } = useSession();
3638
+ const { logout } = useAuth();
3639
+ const { localization } = useAuthLocalization();
3640
+ const handleSignOut = useCallback(async () => {
3641
+ await logout();
3642
+ }, [logout]);
3643
+ if (!user) return null;
3644
+ return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [
3645
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "ghost", children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
3646
+ user.avatar ? /* @__PURE__ */ jsx("img", { src: user.avatar, alt: "", className: "h-8 w-8 rounded-full" }) : /* @__PURE__ */ jsx("span", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-gray-200 text-xs font-medium", children: getInitials(user.name) }),
3647
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: user.name })
3648
+ ] }) }) }),
3649
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", children: [
3650
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { children: /* @__PURE__ */ jsxs("div", { children: [
3651
+ /* @__PURE__ */ jsx("p", { className: "text-sm", children: user.email }),
3652
+ showOrganization && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500", children: localization.ORGANIZATION })
3653
+ ] }) }),
3654
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
3655
+ menuItems?.map((item) => /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: item.onClick, children: item.label }, item.label)),
3656
+ menuItems && menuItems.length > 0 && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
3657
+ /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: handleSignOut, children: localization.SIGN_OUT })
3658
+ ] })
3659
+ ] }) });
3660
+ }
3661
+
3662
+ export { AUTH_ERROR_CODES, AccountSwitcher, AuthContext, AuthProvider, AuthView, DeviceList, EmailOTPForm, EmailVerificationForm, ForgotPasswordForm, LoadingBoundary, LocalizationContext, LoginActivityFeed, LoginForm, MagicLinkForm, OAuthButton, OrganizationMenu, PhoneLoginForm, RISK_COLORS, RecoverAccountForm, ResetPasswordForm, SessionGuard, SignupForm, TwoFactorForm, UserMenu, authLocalization, authViewPaths, backupCodeSchema, credentialLoginSchema, emailOtpCodeSchema, emailOtpEmailSchema, forgotPasswordSchema, getInitials, getLocalizedError, getRiskLevel, isEmail, magicLinkSchema, maskEmail, otpVerifySchema, phoneLoginSchema, phoneNumberSchema, otpVerifySchema2 as phoneOtpVerifySchema, phonePasswordSchema, recoverCodeSchema, recoverEmailSchema, recoverNewPasswordSchema, recoverPhoneSchema, resetPasswordSchema, signupSchema, toAuthError, totpSchema, useApiKeys, useAuth, useAuthContext, useAuthLocalization, useDevices, useLoginActivity, useOrganizations, usePhone, useSession, useSessions, useUser };
752
3663
  //# sourceMappingURL=index.js.map
753
3664
  //# sourceMappingURL=index.js.map