@atzentis/auth-react 0.0.14 → 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,8 @@
1
1
  import { AuthClient, AuthError, AuthErrorCode } from '@atzentis/auth-sdk';
2
- import { createContext, useState, useCallback, useMemo, useContext, useEffect } from 'react';
2
+ import { createContext, useState, useCallback, useMemo, useContext, useEffect, useRef } from 'react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
- import { DropdownMenu, DropdownMenuTrigger, Button, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, Card, DropdownMenuLabel } from '@atzentis/ui-shadcn';
5
- import { FormSelect, FormInput, OTPInput, FormPasswordInput } from '@atzentis/ui-shared';
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
6
  import { zodResolver } from '@hookform/resolvers/zod';
7
7
  import { useForm } from 'react-hook-form';
8
8
  import { z } from 'zod';
@@ -280,10 +280,54 @@ var authLocalization = {
280
280
  RESET_PASSWORD_ACTION: "Save new password",
281
281
  /** @default "Password reset successfully" */
282
282
  RESET_PASSWORD_SUCCESS: "Password reset successfully",
283
+ /** @default "Confirm new password" */
284
+ CONFIRM_NEW_PASSWORD: "Confirm new password",
283
285
  /** @default "Set Password" */
284
286
  SET_PASSWORD: "Set Password",
285
287
  /** @default "Click the button to receive an email to set your password" */
286
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}}",
287
331
  // --- Devices & Sessions ---
288
332
  /** @default "Current Session" */
289
333
  CURRENT_SESSION: "Current Session",
@@ -414,6 +458,15 @@ var authLocalization = {
414
458
  BACKUP_CODES: "Backup Codes",
415
459
  /** @default "Save these codes in a secure place" */
416
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",
417
470
  // --- Validation ---
418
471
  /** @default "Passwords do not match" */
419
472
  PASSWORDS_DO_NOT_MATCH: "Passwords do not match",
@@ -610,25 +663,55 @@ function useAuth() {
610
663
  }
611
664
  return {};
612
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
+ );
613
692
  return {
614
693
  user,
615
694
  isAuthenticated,
616
695
  isLoading,
617
696
  error,
697
+ clearError,
698
+ getAccessToken,
699
+ getAuthHeader,
700
+ getOAuthUrl,
701
+ isUsernameAvailable,
618
702
  login,
619
703
  loginWithUsername,
620
- signup,
621
704
  logout,
622
705
  logoutAllDevices,
623
- getOAuthUrl,
624
- verifyOAuthCode,
706
+ refreshToken,
707
+ requestPasswordReset,
708
+ resetPassword,
625
709
  sendMagicLink,
710
+ sendVerificationEmail,
711
+ signup,
712
+ verifyEmail,
626
713
  verifyMagicLink,
627
- isUsernameAvailable,
628
- getAccessToken,
629
- refreshToken,
630
- getAuthHeader,
631
- clearError
714
+ verifyOAuthCode
632
715
  };
633
716
  }
634
717
  function useAuthLocalization() {
@@ -1213,264 +1296,494 @@ function SessionGuard({
1213
1296
  }
1214
1297
  return className ? /* @__PURE__ */ jsx("div", { className, children }) : /* @__PURE__ */ jsx(Fragment, { children });
1215
1298
  }
1216
- var DEFAULT_DEVICE_ICONS = {
1217
- mobile: "\u{1F4F1}",
1218
- desktop: "\u{1F4BB}",
1219
- tablet: "\u{1F4DF}"
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"
1220
1310
  };
1221
- function DeviceList({
1222
- showTrustToggle = false,
1223
- showRemoveAction = false,
1224
- onDeviceTrusted,
1225
- onDeviceUntrusted,
1226
- onDeviceRemoved,
1227
- renderDeviceIcon,
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,
1228
1346
  className
1229
1347
  }) {
1230
- const { devices, isLoading, error, refresh, trustDevice, untrustDevice, removeDevice } = useDevices();
1231
- const { localization } = useAuthLocalization();
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
+ });
1232
1364
  useEffect(() => {
1233
- refresh();
1234
- }, [refresh]);
1235
- const handleTrustToggle = useCallback(
1236
- async (device) => {
1237
- if (device.isTrusted) {
1238
- await untrustDevice(device.id);
1239
- onDeviceUntrusted?.(device);
1240
- } else {
1241
- await trustDevice(device.id);
1242
- onDeviceTrusted?.(device);
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);
1243
1385
  }
1244
1386
  },
1245
- [trustDevice, untrustDevice, onDeviceTrusted, onDeviceUntrusted]
1387
+ [sendVerificationEmail, redirectUrl, resendCooldown, onError, localization, localizeErrors]
1246
1388
  );
1247
- const handleRemove = useCallback(
1248
- async (device) => {
1249
- await removeDevice(device.id);
1250
- onDeviceRemoved?.(device);
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
+ }
1251
1402
  },
1252
- [removeDevice, onDeviceRemoved]
1403
+ [verifyEmail, onSuccess, onError, localization, localizeErrors]
1253
1404
  );
1254
- if (isLoading && devices.length === 0) {
1255
- return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsx("div", { className: "p-6 text-center text-sm text-gray-500", children: localization.LOADING_DEVICES }) });
1256
- }
1257
- if (error) {
1258
- return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsx("div", { className: "p-6 text-center text-sm text-red-600", children: error.message }) });
1259
- }
1260
- if (devices.length === 0) {
1261
- return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsx("div", { className: "p-6 text-center text-sm text-gray-500", children: localization.NO_DEVICES }) });
1262
- }
1263
- return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsx("div", { className: "p-6 space-y-4", children: devices.map((device) => /* @__PURE__ */ jsxs(
1264
- "div",
1265
- {
1266
- className: "flex items-start justify-between gap-4 rounded border p-4",
1267
- children: [
1268
- /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
1269
- /* @__PURE__ */ jsx("span", { className: "text-2xl", children: renderDeviceIcon ? renderDeviceIcon(device) : DEFAULT_DEVICE_ICONS[device.deviceType] ?? "\u{1F4BB}" }),
1270
- /* @__PURE__ */ jsxs("div", { children: [
1271
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1272
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: device.name }),
1273
- device.isCurrent && /* @__PURE__ */ jsx("span", { className: "rounded bg-blue-100 px-2 py-0.5 text-xs text-blue-700", children: localization.CURRENT_DEVICE })
1274
- ] }),
1275
- /* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-500", children: [
1276
- device.browser,
1277
- " \xB7 ",
1278
- device.os
1279
- ] }),
1280
- (device.lastCity || device.lastCountry) && /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: [device.lastCity, device.lastCountry].filter(Boolean).join(", ") }),
1281
- /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-400", children: [
1282
- localization.LAST_USED,
1283
- " ",
1284
- device.lastUsedAt,
1285
- " \xB7 ",
1286
- localization.IP_LABEL,
1287
- " ",
1288
- device.lastIp
1289
- ] })
1290
- ] })
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
+ )
1291
1462
  ] }),
1292
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1293
- showTrustToggle && /* @__PURE__ */ jsx(
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(
1294
1474
  Button,
1295
1475
  {
1296
1476
  type: "button",
1297
1477
  variant: "outline",
1298
- size: "sm",
1299
- onClick: () => handleTrustToggle(device),
1300
- children: device.isTrusted ? localization.UNTRUST_DEVICE : localization.TRUST_DEVICE
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
1301
1482
  }
1302
1483
  ),
1303
- showRemoveAction && /* @__PURE__ */ jsx(
1304
- Button,
1305
- {
1306
- type: "button",
1307
- variant: "destructive",
1308
- size: "sm",
1309
- disabled: device.isCurrent,
1310
- onClick: () => handleRemove(device),
1311
- children: localization.REMOVE
1312
- }
1313
- )
1484
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: handleGoBack, className: "w-full", children: localization.GO_BACK })
1314
1485
  ] })
1315
- ]
1316
- },
1317
- device.id
1318
- )) }) });
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
+ ] }) });
1319
1518
  }
1320
- function LoadingBoundary({
1321
- children,
1322
- spinner = /* @__PURE__ */ jsx("div", { children: "Loading..." }),
1323
- delay = 0,
1519
+ function EmailVerificationForm({
1520
+ token,
1521
+ email,
1522
+ redirectUrl,
1523
+ onSuccess,
1524
+ onError,
1525
+ resendCooldown = 60,
1324
1526
  className
1325
1527
  }) {
1326
- const { isLoading } = useSession();
1327
- const [showSpinner, setShowSpinner] = useState(delay === 0 && isLoading);
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);
1328
1536
  useEffect(() => {
1329
- if (!isLoading) {
1330
- setShowSpinner(false);
1331
- return;
1332
- }
1333
- if (delay === 0) {
1334
- setShowSpinner(true);
1335
- return;
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);
1336
1573
  }
1337
- const timer = setTimeout(() => {
1338
- setShowSpinner(true);
1339
- }, delay);
1340
- return () => clearTimeout(timer);
1341
- }, [isLoading, delay]);
1342
- if (isLoading && showSpinner) {
1343
- return className ? /* @__PURE__ */ jsx("div", { className, children: spinner }) : /* @__PURE__ */ jsx(Fragment, { children: spinner });
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
+ ] });
1344
1594
  }
1345
- if (isLoading) {
1346
- return null;
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
+ ] });
1347
1631
  }
1348
- return /* @__PURE__ */ jsx(Fragment, { children });
1349
- }
1350
- function SubmitButton({ loading, loadingLabel, actionLabel, className }) {
1351
- return /* @__PURE__ */ jsx(Button, { type: "submit", disabled: loading, className: className ?? "w-full", children: loading ? loadingLabel : actionLabel });
1352
- }
1353
-
1354
- // src/helpers/risk-utils.ts
1355
- function getRiskLevel(score) {
1356
- if (score < 30) return "low";
1357
- if (score < 60) return "medium";
1358
- return "high";
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
+ ] });
1359
1657
  }
1360
- var RISK_COLORS = {
1361
- low: "text-green-600 bg-green-50",
1362
- medium: "text-yellow-600 bg-yellow-50",
1363
- high: "text-red-600 bg-red-50"
1364
- };
1365
- function LoginActivityFeed({
1366
- limit = 20,
1367
- showRiskBadge = false,
1368
- showReportActions = false,
1369
- filterByStatus,
1370
- onSuspiciousReported,
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,
1371
1667
  className
1372
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);
1373
1676
  const {
1374
- events,
1375
- hasMore,
1376
- isLoading,
1377
- error,
1378
- refresh,
1379
- loadMore,
1380
- filter,
1381
- markRecognized,
1382
- reportSuspicious
1383
- } = useLoginActivity({ limit });
1384
- const { localization } = useAuthLocalization();
1677
+ register,
1678
+ handleSubmit,
1679
+ formState: { errors },
1680
+ getValues
1681
+ } = useForm({
1682
+ resolver: zodResolver(forgotPasswordSchema),
1683
+ defaultValues: { email: "" }
1684
+ });
1385
1685
  useEffect(() => {
1386
- if (filterByStatus) {
1387
- filter({ status: filterByStatus, limit });
1388
- }
1389
- refresh();
1390
- }, [refresh, filter, filterByStatus, limit]);
1391
- const handleMarkRecognized = useCallback(
1392
- async (event) => {
1393
- await markRecognized(event.id);
1394
- },
1395
- [markRecognized]
1396
- );
1397
- const handleReportSuspicious = useCallback(
1398
- async (event) => {
1399
- await reportSuspicious(event.id);
1400
- onSuspiciousReported?.(event);
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
+ }
1401
1708
  },
1402
- [reportSuspicious, onSuspiciousReported]
1709
+ [
1710
+ requestPasswordReset,
1711
+ redirectUrl,
1712
+ resendCooldown,
1713
+ onSuccess,
1714
+ onError,
1715
+ localization,
1716
+ localizeErrors
1717
+ ]
1403
1718
  );
1404
- if (isLoading && events.length === 0) {
1405
- return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsx("div", { className: "p-6 text-center text-sm text-gray-500", children: localization.LOADING_ACTIVITY }) });
1406
- }
1407
- if (error) {
1408
- return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsx("div", { className: "p-6 text-center text-sm text-red-600", children: error.message }) });
1409
- }
1410
- if (events.length === 0) {
1411
- return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsx("div", { className: "p-6 text-center text-sm text-gray-500", children: localization.NO_ACTIVITY }) });
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
+ ] }) });
1412
1755
  }
1413
1756
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-4", children: [
1414
- events.map((event) => {
1415
- const riskLevel = getRiskLevel(event.riskScore);
1416
- return /* @__PURE__ */ jsxs(
1417
- "div",
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,
1418
1764
  {
1419
- className: "flex items-start justify-between gap-4 rounded border p-4",
1420
- children: [
1421
- /* @__PURE__ */ jsxs("div", { children: [
1422
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1423
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: event.deviceName }),
1424
- /* @__PURE__ */ jsx("span", { className: "rounded bg-gray-100 px-2 py-0.5 text-xs text-gray-600", children: event.status }),
1425
- showRiskBadge && /* @__PURE__ */ jsx("span", { className: `rounded px-2 py-0.5 text-xs ${RISK_COLORS[riskLevel]}`, children: riskLevel })
1426
- ] }),
1427
- /* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-500", children: [
1428
- event.method,
1429
- " \xB7 ",
1430
- event.ip
1431
- ] }),
1432
- (event.city || event.country) && /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: [event.city, event.country].filter(Boolean).join(", ") }),
1433
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: event.createdAt })
1434
- ] }),
1435
- showReportActions && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1436
- /* @__PURE__ */ jsx(
1437
- Button,
1438
- {
1439
- type: "button",
1440
- variant: "outline",
1441
- size: "sm",
1442
- onClick: () => handleMarkRecognized(event),
1443
- children: localization.MARK_RECOGNIZED
1444
- }
1445
- ),
1446
- /* @__PURE__ */ jsx(
1447
- Button,
1448
- {
1449
- type: "button",
1450
- variant: "destructive",
1451
- size: "sm",
1452
- onClick: () => handleReportSuspicious(event),
1453
- children: localization.REPORT_SUSPICIOUS
1454
- }
1455
- )
1456
- ] })
1457
- ]
1458
- },
1459
- event.id
1460
- );
1461
- }),
1462
- 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 }) })
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
+ ] })
1463
1785
  ] }) });
1464
1786
  }
1465
- function toAuthError(error) {
1466
- if (error instanceof AuthError) return error;
1467
- const message = error instanceof Error ? error.message : String(error);
1468
- return new AuthError({
1469
- code: AuthErrorCode.NetworkError,
1470
- message,
1471
- statusCode: 0
1472
- });
1473
- }
1474
1787
  var credentialLoginSchema = z.object({
1475
1788
  identity: z.string().min(1, "Email or username is required"),
1476
1789
  password: z.string().min(1, "Password is required"),
@@ -1483,11 +1796,6 @@ var phoneLoginSchema = z.object({
1483
1796
  var otpVerifySchema = z.object({
1484
1797
  code: z.string().length(6, "Code must be 6 digits")
1485
1798
  });
1486
-
1487
- // src/components/shared/utils.ts
1488
- function isEmail(value) {
1489
- return value.includes("@");
1490
- }
1491
1799
  var PROVIDER_NAMES = {
1492
1800
  google: "Google",
1493
1801
  github: "GitHub",
@@ -1869,40 +2177,99 @@ function LoginForm({
1869
2177
  ] })
1870
2178
  ] }) });
1871
2179
  }
1872
- var magicLinkSchema = z.object({
1873
- email: z.string().email("Invalid email address")
2180
+ var recoverEmailSchema = z.object({
2181
+ email: z.string().min(1).email()
1874
2182
  });
1875
- function maskEmail(email) {
1876
- const [local, domain] = email.split("@");
1877
- if (!local || !domain) return email;
1878
- const masked = local.length <= 2 ? `${local[0]}***` : `${local[0]}***${local[local.length - 1]}`;
1879
- return `${masked}@${domain}`;
1880
- }
1881
- function MagicLinkForm({ redirectUrl, onSuccess, onError, className }) {
1882
- const { sendMagicLink } = useAuth();
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();
1883
2206
  const { localization, localizeErrors } = useAuthLocalization();
1884
- const [sent, setSent] = useState(false);
1885
- const [sentEmail, setSentEmail] = useState("");
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);
1886
2212
  const [isSubmitting, setIsSubmitting] = useState(false);
1887
2213
  const [formError, setFormError] = useState(null);
1888
- const {
1889
- register,
1890
- handleSubmit,
1891
- formState: { errors },
1892
- getValues
1893
- } = useForm({
1894
- resolver: zodResolver(magicLinkSchema),
2214
+ const [cooldownRemaining, setCooldownRemaining] = useState(0);
2215
+ const codeRef = useRef("");
2216
+ const emailForm = useForm({
2217
+ resolver: zodResolver(recoverEmailSchema),
1895
2218
  defaultValues: { email: "" }
1896
2219
  });
1897
- const onSubmit = useCallback(
1898
- async (data) => {
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) => {
1899
2258
  setFormError(null);
1900
2259
  setIsSubmitting(true);
2260
+ setSelectedMethod(method);
1901
2261
  try {
1902
- await sendMagicLink({ email: data.email, redirectUrl });
1903
- setSent(true);
1904
- setSentEmail(data.email);
1905
- onSuccess?.();
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
+ }
1906
2273
  } catch (err) {
1907
2274
  setFormError(getLocalizedError(err, localization, localizeErrors));
1908
2275
  onError?.(toAuthError(err));
@@ -1910,53 +2277,544 @@ function MagicLinkForm({ redirectUrl, onSuccess, onError, className }) {
1910
2277
  setIsSubmitting(false);
1911
2278
  }
1912
2279
  },
1913
- [sendMagicLink, redirectUrl, onSuccess, onError, localization, localizeErrors]
1914
- );
1915
- const handleResend = useCallback(async () => {
1916
- const email = getValues("email");
2280
+ [
2281
+ email,
2282
+ redirectUrl,
2283
+ resendCooldown,
2284
+ requestPasswordReset,
2285
+ sendVerificationEmail,
2286
+ onError,
2287
+ localization,
2288
+ localizeErrors
2289
+ ]
2290
+ );
2291
+ const handleResendEmailLink = useCallback(async () => {
1917
2292
  setFormError(null);
1918
2293
  setIsSubmitting(true);
1919
2294
  try {
1920
- await sendMagicLink({ email, redirectUrl });
2295
+ await requestPasswordReset({ email, redirectUrl });
2296
+ setCooldownRemaining(resendCooldown);
1921
2297
  } catch (err) {
1922
2298
  setFormError(getLocalizedError(err, localization, localizeErrors));
1923
2299
  } finally {
1924
2300
  setIsSubmitting(false);
1925
2301
  }
1926
- }, [sendMagicLink, redirectUrl, getValues, localization, localizeErrors]);
1927
- if (sent) {
1928
- return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-4 text-center", children: [
1929
- /* @__PURE__ */ jsxs("p", { className: "text-sm", children: [
1930
- localization.MAGIC_LINK_EMAIL,
1931
- " ",
1932
- /* @__PURE__ */ jsx("strong", { children: maskEmail(sentEmail) })
1933
- ] }),
1934
- /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: localization.MAGIC_LINK_HINT }),
1935
- /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", disabled: isSubmitting, onClick: handleResend, children: isSubmitting ? localization.RESENDING : localization.RESEND }),
1936
- formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError })
1937
- ] }) });
1938
- }
1939
- return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit(onSubmit), className: "p-6 space-y-4", children: [
1940
- /* @__PURE__ */ jsx(
1941
- FormInput,
1942
- {
1943
- label: localization.EMAIL,
1944
- id: "magic-link-email",
1945
- type: "email",
1946
- autoComplete: "email",
1947
- error: errors.email?.message,
1948
- ...register("email")
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);
1949
2341
  }
1950
- ),
1951
- formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
1952
- /* @__PURE__ */ jsx(
1953
- SubmitButton,
1954
- {
1955
- loading: isSubmitting,
1956
- loadingLabel: localization.SENDING,
1957
- actionLabel: localization.MAGIC_LINK_ACTION
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);
1958
2358
  }
1959
- )
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
+ ] })
1960
2818
  ] }) });
1961
2819
  }
1962
2820
  var signupSchema = z.object({
@@ -2120,6 +2978,630 @@ function SignupForm({
2120
2978
  ] })
2121
2979
  ] }) });
2122
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
+ }
2123
3605
  function OrganizationMenu({
2124
3606
  showCreateNew = false,
2125
3607
  currentOrgId,
@@ -2177,6 +3659,6 @@ function UserMenu({ showOrganization = false, menuItems, className }) {
2177
3659
  ] }) });
2178
3660
  }
2179
3661
 
2180
- export { AUTH_ERROR_CODES, AccountSwitcher, AuthContext, AuthProvider, DeviceList, LoadingBoundary, LocalizationContext, LoginActivityFeed, LoginForm, MagicLinkForm, OAuthButton, OrganizationMenu, PhoneLoginForm, RISK_COLORS, SessionGuard, SignupForm, UserMenu, authLocalization, credentialLoginSchema, getInitials, getLocalizedError, getRiskLevel, magicLinkSchema, otpVerifySchema, phoneLoginSchema, phoneNumberSchema, otpVerifySchema2 as phoneOtpVerifySchema, phonePasswordSchema, signupSchema, toAuthError, useApiKeys, useAuth, useAuthContext, useAuthLocalization, useDevices, useLoginActivity, useOrganizations, usePhone, useSession, useSessions, useUser };
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 };
2181
3663
  //# sourceMappingURL=index.js.map
2182
3664
  //# sourceMappingURL=index.js.map