@retinalabsllc/zairusjs 11.0.1 → 11.0.2

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.d.mts CHANGED
@@ -71,10 +71,15 @@ interface PipleAuthProps {
71
71
  }) => Promise<{
72
72
  success: boolean;
73
73
  error?: string;
74
+ has2FA?: boolean;
75
+ hasPasskey?: boolean;
76
+ passkeyOptions?: any;
74
77
  }>;
75
78
  onVerifyOtp: (payload: {
76
79
  email: string;
77
- code: string;
80
+ code?: string;
81
+ isPasskey?: boolean;
82
+ passkeyCredential?: any;
78
83
  }) => Promise<{
79
84
  success: boolean;
80
85
  error?: string;
package/dist/index.d.ts CHANGED
@@ -71,10 +71,15 @@ interface PipleAuthProps {
71
71
  }) => Promise<{
72
72
  success: boolean;
73
73
  error?: string;
74
+ has2FA?: boolean;
75
+ hasPasskey?: boolean;
76
+ passkeyOptions?: any;
74
77
  }>;
75
78
  onVerifyOtp: (payload: {
76
79
  email: string;
77
- code: string;
80
+ code?: string;
81
+ isPasskey?: boolean;
82
+ passkeyCredential?: any;
78
83
  }) => Promise<{
79
84
  success: boolean;
80
85
  error?: string;
package/dist/index.js CHANGED
@@ -369,6 +369,7 @@ var import_react6 = __toESM(require("react"));
369
369
  var import_navigation2 = require("next/navigation");
370
370
  var import_react_hot_toast = __toESM(require("react-hot-toast"));
371
371
  var import_react_google_recaptcha_v3 = require("react-google-recaptcha-v3");
372
+ var import_browser = require("@simplewebauthn/browser");
372
373
  var InputSpinner = () => /* @__PURE__ */ import_react6.default.createElement("svg", { className: "animate-spin h-4 w-4 text-neutral-400", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24" }, /* @__PURE__ */ import_react6.default.createElement("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), /* @__PURE__ */ import_react6.default.createElement("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" }));
373
374
  function AuthFormInner({
374
375
  companyName,
@@ -400,6 +401,9 @@ function AuthFormInner({
400
401
  const [lastName, setLastName] = (0, import_react6.useState)("");
401
402
  const [orgName, setOrgName] = (0, import_react6.useState)("");
402
403
  const [agreedToTerms, setAgreedToTerms] = (0, import_react6.useState)(false);
404
+ const [userHas2FA, setUserHas2FA] = (0, import_react6.useState)(false);
405
+ const [userHasPasskey, setUserHasPasskey] = (0, import_react6.useState)(false);
406
+ const [authOptions, setAuthOptions] = (0, import_react6.useState)(null);
403
407
  const [otp, setOtp] = (0, import_react6.useState)(["", "", "", "", "", ""]);
404
408
  const inputRefs = (0, import_react6.useRef)([]);
405
409
  const cleanAlpha = (val) => val.replace(/[^a-zA-Z\s-]/g, "");
@@ -417,9 +421,7 @@ function AuthFormInner({
417
421
  const newOtp = [...otp];
418
422
  newOtp[index] = cleanValue.slice(-1);
419
423
  setOtp(newOtp);
420
- if (cleanValue && index < 5) {
421
- inputRefs.current[index + 1]?.focus();
422
- }
424
+ if (cleanValue && index < 5) inputRefs.current[index + 1]?.focus();
423
425
  };
424
426
  const handlePaste = (e) => {
425
427
  e.preventDefault();
@@ -428,13 +430,10 @@ function AuthFormInner({
428
430
  if (!numbersOnly) return;
429
431
  const newOtp = [...otp];
430
432
  const length = Math.min(numbersOnly.length, 6);
431
- for (let i = 0; i < length; i++) {
432
- newOtp[i] = numbersOnly[i];
433
- }
433
+ for (let i = 0; i < length; i++) newOtp[i] = numbersOnly[i];
434
434
  setOtp(newOtp);
435
- if (length < 6) {
436
- inputRefs.current[length]?.focus();
437
- } else {
435
+ if (length < 6) inputRefs.current[length]?.focus();
436
+ else {
438
437
  inputRefs.current[5]?.focus();
439
438
  verifyOtpCode(newOtp.join(""));
440
439
  }
@@ -461,7 +460,10 @@ function AuthFormInner({
461
460
  recaptchaToken
462
461
  });
463
462
  if (res.success) {
464
- import_react_hot_toast.default.success("Verification code sent");
463
+ setUserHas2FA(!!res.has2FA);
464
+ setUserHasPasskey(!!res.hasPasskey);
465
+ if (res.passkeyOptions) setAuthOptions(res.passkeyOptions);
466
+ import_react_hot_toast.default.success(res.has2FA ? "Security check required" : "Verification code sent");
465
467
  setStep("OTP");
466
468
  setCountdown(60);
467
469
  } else {
@@ -491,6 +493,23 @@ function AuthFormInner({
491
493
  setIsSubmitting(false);
492
494
  }
493
495
  };
496
+ const triggerPasskeyLogin = async () => {
497
+ if (!authOptions || isSubmitting) return;
498
+ setIsSubmitting(true);
499
+ try {
500
+ const credentialResponse = await (0, import_browser.startAuthentication)({ optionsJSON: authOptions });
501
+ const res = await onVerifyOtp({ email: emailId, isPasskey: true, passkeyCredential: credentialResponse });
502
+ if (res.success) {
503
+ window.location.href = res.redirect || redirectUrl;
504
+ } else {
505
+ import_react_hot_toast.default.error(res.error || "Device verification failed.");
506
+ }
507
+ } catch (err) {
508
+ if (err.name !== "NotAllowedError") import_react_hot_toast.default.error("Passkey verification interrupted.");
509
+ } finally {
510
+ setIsSubmitting(false);
511
+ }
512
+ };
494
513
  const handleNextSignupStep = () => {
495
514
  if (signupStep === "NAME") {
496
515
  if (requireOrganization) setSignupStep("ORGANIZATION");
@@ -509,7 +528,7 @@ function AuthFormInner({
509
528
  }
510
529
  return false;
511
530
  };
512
- return /* @__PURE__ */ import_react6.default.createElement("div", { className: "w-full max-w-md mx-auto flex flex-col items-center gap-4 relative z-10 animate-in fade-in duration-300" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "w-full bg-white rounded-2xl overflow-hidden" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "p-8 md:p-12" }, step === "INPUT" && /* @__PURE__ */ import_react6.default.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "mb-12 text-center mt-2" }, /* @__PURE__ */ import_react6.default.createElement("h2", { className: " text-xl text-black mb-2 tracking-tight " }, mode === "LOGIN" ? `${companyName} ${workspaceLabel}` : "Create Account"), /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-[13px] text-neutral-500" }, mode === "LOGIN" ? /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, null, "Don't have an account? ", /* @__PURE__ */ import_react6.default.createElement("button", { type: "button", onClick: () => {
531
+ return /* @__PURE__ */ import_react6.default.createElement("div", { className: "w-full max-w-md mx-auto flex flex-col items-center gap-4 relative z-10 animate-in fade-in duration-300" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "w-full bg-white rounded-2xl overflow-hidden" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "p-8 md:p-12" }, step === "INPUT" && /* @__PURE__ */ import_react6.default.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "mb-12 text-center mt-2" }, /* @__PURE__ */ import_react6.default.createElement("h2", { className: "text-xl text-black mb-2 tracking-tight" }, mode === "LOGIN" ? `${companyName} ${workspaceLabel}` : "Create Account"), /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-[13px] text-neutral-500" }, mode === "LOGIN" ? /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, null, "Don't have an account? ", /* @__PURE__ */ import_react6.default.createElement("button", { type: "button", onClick: () => {
513
532
  setMode("SIGNUP");
514
533
  setSignupStep(getInitialSignupStep());
515
534
  }, className: "text-black transition-colors ml-1" }, "Sign up")) : /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, null, "Already have an account? ", /* @__PURE__ */ import_react6.default.createElement("button", { type: "button", onClick: () => setMode("LOGIN"), className: "text-black transition-colors ml-1" }, "Log in")))), /* @__PURE__ */ import_react6.default.createElement("form", { className: "space-y-6", autoComplete: "off", onSubmit: (e) => {
@@ -517,69 +536,7 @@ function AuthFormInner({
517
536
  if (mode === "SIGNUP" && signupStep === "NAME") handleNextSignupStep();
518
537
  else if (mode === "SIGNUP" && signupStep === "ORGANIZATION") handleNextSignupStep();
519
538
  else handleAuthRequestSubmit();
520
- } }, mode === "SIGNUP" && signupStep === "NAME" && requireNames && /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex flex-col gap-6" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react6.default.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "First Name"), /* @__PURE__ */ import_react6.default.createElement(
521
- "input",
522
- {
523
- type: "text",
524
- value: firstName,
525
- onChange: (e) => setFirstName(cleanAlpha(e.target.value)),
526
- required: true,
527
- autoFocus: true,
528
- className: "w-full px-2 py-3 text-sm bg-transparent border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300",
529
- placeholder: "First name"
530
- }
531
- )), /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react6.default.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Last Name"), /* @__PURE__ */ import_react6.default.createElement(
532
- "input",
533
- {
534
- type: "text",
535
- value: lastName,
536
- onChange: (e) => setLastName(cleanAlpha(e.target.value)),
537
- required: true,
538
- className: "w-full px-2 py-3 bg-transparent text-sm border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300",
539
- placeholder: "Last name"
540
- }
541
- ))), mode === "SIGNUP" && signupStep === "ORGANIZATION" && requireOrganization && /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex flex-col gap-6" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-1.5 relative" }, /* @__PURE__ */ import_react6.default.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Organization Name"), /* @__PURE__ */ import_react6.default.createElement(
542
- "input",
543
- {
544
- type: "text",
545
- value: orgName,
546
- onChange: (e) => setOrgName(cleanOrgName(e.target.value)),
547
- required: true,
548
- autoFocus: true,
549
- className: "w-full px-2 py-3 text-sm bg-transparent border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300",
550
- placeholder: "Acme Corporation"
551
- }
552
- ))), (mode === "LOGIN" || mode === "SIGNUP" && signupStep === "EMAIL_ID") && /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react6.default.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Email ID"), /* @__PURE__ */ import_react6.default.createElement(
553
- "input",
554
- {
555
- type: "email",
556
- value: emailId,
557
- onChange: (e) => setEmailId(cleanEmailId(e.target.value)),
558
- required: true,
559
- autoFocus: true,
560
- className: "w-full px-2 py-3 bg-transparent text-sm border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300",
561
- placeholder: "name@company.com"
562
- }
563
- )), mode === "SIGNUP" && /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex items-start gap-3 mt-4" }, /* @__PURE__ */ import_react6.default.createElement(
564
- "input",
565
- {
566
- type: "checkbox",
567
- id: "zairus-terms",
568
- checked: agreedToTerms,
569
- onChange: (e) => setAgreedToTerms(e.target.checked),
570
- className: "mt-0.5 w-4 h-4 bg-white border-neutral-300 rounded text-black focus:ring-black cursor-pointer",
571
- required: true
572
- }
573
- ), /* @__PURE__ */ import_react6.default.createElement("label", { htmlFor: "zairus-terms", className: "text-[11px] text-neutral-500 cursor-pointer leading-snug" }, "I agree to ", companyName, "'s ", /* @__PURE__ */ import_react6.default.createElement("a", { href: termsUrl, target: "_blank", rel: "noreferrer", className: "text-black underline " }, "Terms of Service"), " and ", /* @__PURE__ */ import_react6.default.createElement("a", { href: privacyUrl, target: "_blank", rel: "noreferrer", className: "text-black underline " }, "Privacy Policy"), "."))), /* @__PURE__ */ import_react6.default.createElement(
574
- ThreeDActionButton,
575
- {
576
- type: "submit",
577
- disabled: isContinueDisabled(),
578
- isLoading: isSubmitting,
579
- className: "w-full mt-10"
580
- },
581
- "Continue"
582
- ))), step === "OTP" && /* @__PURE__ */ import_react6.default.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-center mb-10 mt-2" }, /* @__PURE__ */ import_react6.default.createElement("h2", { className: " text-xl text-black mb-2 tracking-tight " }, "Security Check"), /* @__PURE__ */ import_react6.default.createElement("p", { className: "text-[13px] text-neutral-500" }, "Enter the code sent to ", /* @__PURE__ */ import_react6.default.createElement("br", null), /* @__PURE__ */ import_react6.default.createElement("span", { className: "text-black " }, emailId))), /* @__PURE__ */ import_react6.default.createElement("form", { className: "space-y-10", autoComplete: "off", onSubmit: (e) => {
539
+ } }, mode === "SIGNUP" && signupStep === "NAME" && requireNames && /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex flex-col gap-6" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react6.default.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "First Name"), /* @__PURE__ */ import_react6.default.createElement("input", { type: "text", value: firstName, onChange: (e) => setFirstName(cleanAlpha(e.target.value)), required: true, autoFocus: true, className: "w-full px-2 py-3 text-sm bg-transparent border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300", placeholder: "First name" })), /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react6.default.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Last Name"), /* @__PURE__ */ import_react6.default.createElement("input", { type: "text", value: lastName, onChange: (e) => setLastName(cleanAlpha(e.target.value)), required: true, className: "w-full px-2 py-3 bg-transparent text-sm border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300", placeholder: "Last name" }))), mode === "SIGNUP" && signupStep === "ORGANIZATION" && requireOrganization && /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex flex-col gap-6" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-1.5 relative" }, /* @__PURE__ */ import_react6.default.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Organization Name"), /* @__PURE__ */ import_react6.default.createElement("input", { type: "text", value: orgName, onChange: (e) => setOrgName(cleanOrgName(e.target.value)), required: true, autoFocus: true, className: "w-full px-2 py-3 text-sm bg-transparent border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300", placeholder: "Acme Corporation" }))), (mode === "LOGIN" || mode === "SIGNUP" && signupStep === "EMAIL_ID") && /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react6.default.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Email ID"), /* @__PURE__ */ import_react6.default.createElement("input", { type: "email", value: emailId, onChange: (e) => setEmailId(cleanEmailId(e.target.value)), required: true, autoFocus: true, className: "w-full px-2 py-3 bg-transparent text-sm border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300", placeholder: "name@company.com" })), mode === "SIGNUP" && /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex items-start gap-3 mt-4" }, /* @__PURE__ */ import_react6.default.createElement("input", { type: "checkbox", id: "zairus-terms", checked: agreedToTerms, onChange: (e) => setAgreedToTerms(e.target.checked), className: "mt-0.5 w-4 h-4 bg-white border-neutral-300 rounded text-black focus:ring-black cursor-pointer", required: true }), /* @__PURE__ */ import_react6.default.createElement("label", { htmlFor: "zairus-terms", className: "text-[11px] text-neutral-500 cursor-pointer leading-snug" }, "I agree to ", companyName, "'s ", /* @__PURE__ */ import_react6.default.createElement("a", { href: termsUrl, target: "_blank", rel: "noreferrer", className: "text-black underline " }, "Terms of Service"), " and ", /* @__PURE__ */ import_react6.default.createElement("a", { href: privacyUrl, target: "_blank", rel: "noreferrer", className: "text-black underline " }, "Privacy Policy"), "."))), /* @__PURE__ */ import_react6.default.createElement(ThreeDActionButton, { type: "submit", disabled: isContinueDisabled(), isLoading: isSubmitting, className: "w-full mt-10" }, "Continue"))), step === "OTP" && /* @__PURE__ */ import_react6.default.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-center mb-10 mt-2" }, /* @__PURE__ */ import_react6.default.createElement("h2", { className: "text-xl text-black mb-2 tracking-tight" }, "Security Check"), /* @__PURE__ */ import_react6.default.createElement("p", { className: "text-[13px] text-neutral-500" }, userHas2FA ? "Enter the code from your authenticator app." : `Enter the code sent to ${emailId}`)), /* @__PURE__ */ import_react6.default.createElement("form", { className: "space-y-8", autoComplete: "off", onSubmit: (e) => {
583
540
  e.preventDefault();
584
541
  verifyOtpCode(otp.join(""));
585
542
  } }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex justify-between gap-2", onPaste: handlePaste }, otp.map((digit, index) => /* @__PURE__ */ import_react6.default.createElement(
@@ -594,17 +551,19 @@ function AuthFormInner({
594
551
  maxLength: 1,
595
552
  value: digit,
596
553
  onChange: (e) => handleOtpChange(index, e.target.value),
597
- className: "w-8 h-8 text-center text-lg bg-transparent border-b-2 border-neutral-100 text-black outline-none focus:border-black transition-all duration-300"
554
+ onKeyDown: (e) => {
555
+ if (e.key === "Backspace" && !otp[index] && index > 0) inputRefs.current[index - 1]?.focus();
556
+ },
557
+ className: "w-10 h-12 text-center text-lg bg-transparent border-b-2 border-neutral-100 text-black outline-none focus:border-black transition-all duration-300"
598
558
  }
599
- ))), /* @__PURE__ */ import_react6.default.createElement(
600
- ThreeDActionButton,
559
+ ))), /* @__PURE__ */ import_react6.default.createElement(ThreeDActionButton, { type: "submit", disabled: otp.join("").length < 6, isLoading: isSubmitting, className: "w-full" }, "Verify Code")), userHasPasskey && /* @__PURE__ */ import_react6.default.createElement("div", { className: "mt-8 pt-6 " }, /* @__PURE__ */ import_react6.default.createElement(
560
+ "button",
601
561
  {
602
- type: "submit",
603
- disabled: otp.join("").length < 6,
604
- isLoading: isSubmitting,
605
- className: "w-full"
562
+ onClick: triggerPasskeyLogin,
563
+ disabled: isSubmitting,
564
+ className: "w-full py-3 rounded-full text-black bg-neutral-100 hover:bg-neutral-200 text-[13px] font-medium tracking-wide transition-colors outline-none disabled:opacity-50"
606
565
  },
607
- "Verify Code"
566
+ "Use Passkey / Biometrics"
608
567
  ))))));
609
568
  }
610
569
  var PipleAuth = (props) => {
@@ -2665,7 +2624,7 @@ var import_react45 = __toESM(require("react"));
2665
2624
  var import_react_hot_toast8 = __toESM(require("react-hot-toast"));
2666
2625
  var import_react46 = require("@hugeicons/react");
2667
2626
  var import_core_free_icons16 = require("@hugeicons/core-free-icons");
2668
- var import_browser = require("@simplewebauthn/browser");
2627
+ var import_browser2 = require("@simplewebauthn/browser");
2669
2628
  var UniversalSecurityPage = ({
2670
2629
  has2FA,
2671
2630
  hasPasskey,
@@ -2810,7 +2769,7 @@ var UniversalSecurityPage = ({
2810
2769
  const options = await onRequestPasskeySetup();
2811
2770
  let credentialResponse;
2812
2771
  try {
2813
- credentialResponse = await (0, import_browser.startRegistration)({ optionsJSON: options });
2772
+ credentialResponse = await (0, import_browser2.startRegistration)({ optionsJSON: options });
2814
2773
  } catch (err) {
2815
2774
  if (err.name === "NotAllowedError") {
2816
2775
  throw new Error("Passkey setup was canceled or is not supported on this device.");
@@ -2861,7 +2820,15 @@ var UniversalSecurityPage = ({
2861
2820
  toggleLoading: isPasskeyLoading,
2862
2821
  isLast: true
2863
2822
  }
2864
- ))), show2FASetupModal && /* @__PURE__ */ import_react45.default.createElement("div", { className: "fixed inset-0 z-120 flex items-center justify-center p-4 pointer-events-auto" }, /* @__PURE__ */ import_react45.default.createElement("div", { className: "absolute inset-0 bg-black/40 ", onClick: () => !isSubmitting && setShow2FASetupModal(false) }), /* @__PURE__ */ import_react45.default.createElement("div", { className: "relative w-full max-w-sm bg-white rounded-2xl flex flex-col overflow-hidden shadow-2xl animate-in zoom-in-95 duration-200 max-h-[90vh]" }, /* @__PURE__ */ import_react45.default.createElement("div", { className: "flex items-center justify-between p-5 shrink-0 " }, /* @__PURE__ */ import_react45.default.createElement("h3", { className: "text-[15px] text-black tracking-tight" }, "Enable 2FA"), /* @__PURE__ */ import_react45.default.createElement("button", { onClick: () => !isSubmitting && setShow2FASetupModal(false), className: "text-neutral-400 hover:text-black transition-colors outline-none" }, /* @__PURE__ */ import_react45.default.createElement(import_react46.HugeiconsIcon, { icon: import_core_free_icons16.CancelCircleIcon, size: 18 }))), /* @__PURE__ */ import_react45.default.createElement("div", { className: "flex-1 overflow-y-auto custom-scrollbar p-6 pt-2" }, /* @__PURE__ */ import_react45.default.createElement("div", { className: "w-full flex justify-center mb-6 mix-blend-multiply" }, twoFaQr ? /* @__PURE__ */ import_react45.default.createElement("img", { src: twoFaQr, alt: "2FA QR Code", className: "w-40 h-40 object-contain rounded-xl border border-neutral-100 p-2" }) : /* @__PURE__ */ import_react45.default.createElement("div", { className: "w-40 h-40 bg-neutral-100 rounded-xl flex items-center justify-center animate-pulse" })), /* @__PURE__ */ import_react45.default.createElement("div", { className: "text-center mb-8 mt-2" }, /* @__PURE__ */ import_react45.default.createElement("h2", { className: "text-xl text-black mb-2 tracking-tight" }, "Security Check"), /* @__PURE__ */ import_react45.default.createElement("p", { className: "text-[13px] text-neutral-500" }, "Enter the 6-digit code from your app")), /* @__PURE__ */ import_react45.default.createElement("form", { className: "space-y-4", autoComplete: "off", onSubmit: (e) => {
2823
+ ))), show2FASetupModal && /* @__PURE__ */ import_react45.default.createElement("div", { className: "fixed inset-0 z-120 flex items-center justify-center p-4 pointer-events-auto" }, /* @__PURE__ */ import_react45.default.createElement("div", { className: "absolute inset-0 bg-black/40 ", onClick: () => !isSubmitting && setShow2FASetupModal(false) }), /* @__PURE__ */ import_react45.default.createElement("div", { className: "relative w-full max-w-sm bg-white rounded-2xl flex flex-col overflow-hidden shadow-2xl animate-in zoom-in-95 duration-200 max-h-[90vh]" }, /* @__PURE__ */ import_react45.default.createElement("div", { className: "flex items-center justify-between p-5 shrink-0 " }, /* @__PURE__ */ import_react45.default.createElement("h3", { className: "text-[15px] text-black tracking-tight" }, "Enable 2FA"), /* @__PURE__ */ import_react45.default.createElement("button", { onClick: () => !isSubmitting && setShow2FASetupModal(false), className: "text-neutral-400 hover:text-black transition-colors outline-none" }, /* @__PURE__ */ import_react45.default.createElement(import_react46.HugeiconsIcon, { icon: import_core_free_icons16.CancelCircleIcon, size: 18 }))), /* @__PURE__ */ import_react45.default.createElement("div", { className: "flex-1 overflow-y-auto custom-scrollbar p-6 pt-2" }, /* @__PURE__ */ import_react45.default.createElement("div", { className: "w-full flex justify-center mb-6 mix-blend-multiply" }, twoFaQr ? /* @__PURE__ */ import_react45.default.createElement("img", { src: twoFaQr, alt: "2FA QR Code", className: "w-40 h-40 object-contain rounded-xl border border-neutral-100 p-2" }) : /* @__PURE__ */ import_react45.default.createElement("div", { className: "w-40 h-40 bg-neutral-100 rounded-xl flex items-center justify-center animate-pulse" })), /* @__PURE__ */ import_react45.default.createElement("div", { className: "grid grid-cols-1 gap-y-6 gap-x-4 mb-8" }, /* @__PURE__ */ import_react45.default.createElement("div", null, /* @__PURE__ */ import_react45.default.createElement("span", { className: "text-[11px] tracking-[0.2em] text-neutral-400 block mb-2 uppercase" }, "Setup Key"), /* @__PURE__ */ import_react45.default.createElement("div", { className: "flex items-center gap-2 min-w-0 bg-neutral-50 px-3 py-2.5 rounded-xl " }, /* @__PURE__ */ import_react45.default.createElement("span", { className: "text-[13px] font-mono tracking-widest text-black truncate min-w-0 block flex-1" }, twoFaSecret), /* @__PURE__ */ import_react45.default.createElement(
2824
+ "button",
2825
+ {
2826
+ type: "button",
2827
+ onClick: () => handleCopySecret(twoFaSecret),
2828
+ className: "text-neutral-400 hover:text-black transition-colors outline-none shrink-0 ml-2"
2829
+ },
2830
+ /* @__PURE__ */ import_react45.default.createElement(import_react46.HugeiconsIcon, { icon: import_core_free_icons16.Copy01Icon, size: 16 })
2831
+ )))), /* @__PURE__ */ import_react45.default.createElement("div", { className: "text-center mb-8 mt-2" }, /* @__PURE__ */ import_react45.default.createElement("h2", { className: "text-xl text-black mb-2 tracking-tight" }, "Security Check"), /* @__PURE__ */ import_react45.default.createElement("p", { className: "text-[13px] text-neutral-500" }, "Enter the 6-digit code from your app")), /* @__PURE__ */ import_react45.default.createElement("form", { className: "space-y-4", autoComplete: "off", onSubmit: (e) => {
2865
2832
  e.preventDefault();
2866
2833
  submitEnable2FA(otp.join(""));
2867
2834
  } }, /* @__PURE__ */ import_react45.default.createElement("div", { className: "flex justify-between gap-2 pb-6", onPaste: handlePaste }, otp.map((digit, index) => /* @__PURE__ */ import_react45.default.createElement(
@@ -2932,7 +2899,7 @@ var UniversalSecurityPage = ({
2932
2899
  className: "w-full py-3.5"
2933
2900
  },
2934
2901
  "Verify & Disable"
2935
- ))))), showPasskeyModal && /* @__PURE__ */ import_react45.default.createElement("div", { className: "fixed inset-0 z-120 flex items-center justify-center p-4 pointer-events-auto" }, /* @__PURE__ */ import_react45.default.createElement("div", { className: "absolute inset-0 bg-black/40 ", onClick: () => !isSubmitting && setShowPasskeyModal(false) }), /* @__PURE__ */ import_react45.default.createElement("div", { className: "relative w-full max-w-sm bg-white rounded-2xl flex flex-col overflow-hidden shadow-2xl animate-in zoom-in-95 duration-200" }, /* @__PURE__ */ import_react45.default.createElement("div", { className: "flex items-center justify-between p-5 shrink-0" }, /* @__PURE__ */ import_react45.default.createElement("h3", { className: "text-[15px] text-black tracking-tight" }, "Enable Passkey"), /* @__PURE__ */ import_react45.default.createElement("button", { onClick: () => !isSubmitting && setShowPasskeyModal(false), className: "text-neutral-400 hover:text-black transition-colors outline-none" }, /* @__PURE__ */ import_react45.default.createElement(import_react46.HugeiconsIcon, { icon: import_core_free_icons16.CancelCircleIcon, size: 18 }))), /* @__PURE__ */ import_react45.default.createElement("div", { className: "p-6 text-center" }, /* @__PURE__ */ import_react45.default.createElement("p", { className: "text-[13px] text-neutral-500 mb-8 leading-relaxed" }, "Use your device's biometrics (Face ID, Touch ID, or PIN) to securely sign into your account without needing a password."), /* @__PURE__ */ import_react45.default.createElement(
2902
+ ))))), showPasskeyModal && /* @__PURE__ */ import_react45.default.createElement("div", { className: "fixed inset-0 z-120 flex items-center justify-center p-4 pointer-events-auto" }, /* @__PURE__ */ import_react45.default.createElement("div", { className: "absolute inset-0 bg-black/40 ", onClick: () => !isSubmitting && setShowPasskeyModal(false) }), /* @__PURE__ */ import_react45.default.createElement("div", { className: "relative w-full max-w-sm bg-white rounded-2xl flex flex-col overflow-hidden shadow-2xl animate-in zoom-in-95 duration-200" }, /* @__PURE__ */ import_react45.default.createElement("div", { className: "flex items-center justify-between p-5 shrink-0" }, /* @__PURE__ */ import_react45.default.createElement("h3", { className: "text-[15px] text-black tracking-tight" }, "Enable Passkey"), /* @__PURE__ */ import_react45.default.createElement("button", { onClick: () => !isSubmitting && setShowPasskeyModal(false), className: "text-neutral-400 hover:text-black transition-colors outline-none" }, /* @__PURE__ */ import_react45.default.createElement(import_react46.HugeiconsIcon, { icon: import_core_free_icons16.CancelCircleIcon, size: 18 }))), /* @__PURE__ */ import_react45.default.createElement("div", { className: "p-6 text-center" }, /* @__PURE__ */ import_react45.default.createElement("h2", { className: "text-lg text-black mb-2 tracking-tight" }, "Simplify your Sign In"), /* @__PURE__ */ import_react45.default.createElement("p", { className: "text-[13px] text-neutral-500 mb-8 leading-relaxed" }, "Use your device's biometrics (Face ID, Touch ID, or PIN) to securely sign into your account without needing a password."), /* @__PURE__ */ import_react45.default.createElement(
2936
2903
  ThreeDActionButton,
2937
2904
  {
2938
2905
  onClick: submitEnablePasskey,
package/dist/index.mjs CHANGED
@@ -297,6 +297,7 @@ import React5, { useState as useState3, useRef as useRef2, useEffect as useEffec
297
297
  import { useSearchParams } from "next/navigation";
298
298
  import toast from "react-hot-toast";
299
299
  import { useGoogleReCaptcha, GoogleReCaptchaProvider } from "react-google-recaptcha-v3";
300
+ import { startAuthentication } from "@simplewebauthn/browser";
300
301
  var InputSpinner = () => /* @__PURE__ */ React5.createElement("svg", { className: "animate-spin h-4 w-4 text-neutral-400", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24" }, /* @__PURE__ */ React5.createElement("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), /* @__PURE__ */ React5.createElement("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" }));
301
302
  function AuthFormInner({
302
303
  companyName,
@@ -328,6 +329,9 @@ function AuthFormInner({
328
329
  const [lastName, setLastName] = useState3("");
329
330
  const [orgName, setOrgName] = useState3("");
330
331
  const [agreedToTerms, setAgreedToTerms] = useState3(false);
332
+ const [userHas2FA, setUserHas2FA] = useState3(false);
333
+ const [userHasPasskey, setUserHasPasskey] = useState3(false);
334
+ const [authOptions, setAuthOptions] = useState3(null);
331
335
  const [otp, setOtp] = useState3(["", "", "", "", "", ""]);
332
336
  const inputRefs = useRef2([]);
333
337
  const cleanAlpha = (val) => val.replace(/[^a-zA-Z\s-]/g, "");
@@ -345,9 +349,7 @@ function AuthFormInner({
345
349
  const newOtp = [...otp];
346
350
  newOtp[index] = cleanValue.slice(-1);
347
351
  setOtp(newOtp);
348
- if (cleanValue && index < 5) {
349
- inputRefs.current[index + 1]?.focus();
350
- }
352
+ if (cleanValue && index < 5) inputRefs.current[index + 1]?.focus();
351
353
  };
352
354
  const handlePaste = (e) => {
353
355
  e.preventDefault();
@@ -356,13 +358,10 @@ function AuthFormInner({
356
358
  if (!numbersOnly) return;
357
359
  const newOtp = [...otp];
358
360
  const length = Math.min(numbersOnly.length, 6);
359
- for (let i = 0; i < length; i++) {
360
- newOtp[i] = numbersOnly[i];
361
- }
361
+ for (let i = 0; i < length; i++) newOtp[i] = numbersOnly[i];
362
362
  setOtp(newOtp);
363
- if (length < 6) {
364
- inputRefs.current[length]?.focus();
365
- } else {
363
+ if (length < 6) inputRefs.current[length]?.focus();
364
+ else {
366
365
  inputRefs.current[5]?.focus();
367
366
  verifyOtpCode(newOtp.join(""));
368
367
  }
@@ -389,7 +388,10 @@ function AuthFormInner({
389
388
  recaptchaToken
390
389
  });
391
390
  if (res.success) {
392
- toast.success("Verification code sent");
391
+ setUserHas2FA(!!res.has2FA);
392
+ setUserHasPasskey(!!res.hasPasskey);
393
+ if (res.passkeyOptions) setAuthOptions(res.passkeyOptions);
394
+ toast.success(res.has2FA ? "Security check required" : "Verification code sent");
393
395
  setStep("OTP");
394
396
  setCountdown(60);
395
397
  } else {
@@ -419,6 +421,23 @@ function AuthFormInner({
419
421
  setIsSubmitting(false);
420
422
  }
421
423
  };
424
+ const triggerPasskeyLogin = async () => {
425
+ if (!authOptions || isSubmitting) return;
426
+ setIsSubmitting(true);
427
+ try {
428
+ const credentialResponse = await startAuthentication({ optionsJSON: authOptions });
429
+ const res = await onVerifyOtp({ email: emailId, isPasskey: true, passkeyCredential: credentialResponse });
430
+ if (res.success) {
431
+ window.location.href = res.redirect || redirectUrl;
432
+ } else {
433
+ toast.error(res.error || "Device verification failed.");
434
+ }
435
+ } catch (err) {
436
+ if (err.name !== "NotAllowedError") toast.error("Passkey verification interrupted.");
437
+ } finally {
438
+ setIsSubmitting(false);
439
+ }
440
+ };
422
441
  const handleNextSignupStep = () => {
423
442
  if (signupStep === "NAME") {
424
443
  if (requireOrganization) setSignupStep("ORGANIZATION");
@@ -437,7 +456,7 @@ function AuthFormInner({
437
456
  }
438
457
  return false;
439
458
  };
440
- return /* @__PURE__ */ React5.createElement("div", { className: "w-full max-w-md mx-auto flex flex-col items-center gap-4 relative z-10 animate-in fade-in duration-300" }, /* @__PURE__ */ React5.createElement("div", { className: "w-full bg-white rounded-2xl overflow-hidden" }, /* @__PURE__ */ React5.createElement("div", { className: "p-8 md:p-12" }, step === "INPUT" && /* @__PURE__ */ React5.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ React5.createElement("div", { className: "mb-12 text-center mt-2" }, /* @__PURE__ */ React5.createElement("h2", { className: " text-xl text-black mb-2 tracking-tight " }, mode === "LOGIN" ? `${companyName} ${workspaceLabel}` : "Create Account"), /* @__PURE__ */ React5.createElement("div", { className: "text-[13px] text-neutral-500" }, mode === "LOGIN" ? /* @__PURE__ */ React5.createElement(React5.Fragment, null, "Don't have an account? ", /* @__PURE__ */ React5.createElement("button", { type: "button", onClick: () => {
459
+ return /* @__PURE__ */ React5.createElement("div", { className: "w-full max-w-md mx-auto flex flex-col items-center gap-4 relative z-10 animate-in fade-in duration-300" }, /* @__PURE__ */ React5.createElement("div", { className: "w-full bg-white rounded-2xl overflow-hidden" }, /* @__PURE__ */ React5.createElement("div", { className: "p-8 md:p-12" }, step === "INPUT" && /* @__PURE__ */ React5.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ React5.createElement("div", { className: "mb-12 text-center mt-2" }, /* @__PURE__ */ React5.createElement("h2", { className: "text-xl text-black mb-2 tracking-tight" }, mode === "LOGIN" ? `${companyName} ${workspaceLabel}` : "Create Account"), /* @__PURE__ */ React5.createElement("div", { className: "text-[13px] text-neutral-500" }, mode === "LOGIN" ? /* @__PURE__ */ React5.createElement(React5.Fragment, null, "Don't have an account? ", /* @__PURE__ */ React5.createElement("button", { type: "button", onClick: () => {
441
460
  setMode("SIGNUP");
442
461
  setSignupStep(getInitialSignupStep());
443
462
  }, className: "text-black transition-colors ml-1" }, "Sign up")) : /* @__PURE__ */ React5.createElement(React5.Fragment, null, "Already have an account? ", /* @__PURE__ */ React5.createElement("button", { type: "button", onClick: () => setMode("LOGIN"), className: "text-black transition-colors ml-1" }, "Log in")))), /* @__PURE__ */ React5.createElement("form", { className: "space-y-6", autoComplete: "off", onSubmit: (e) => {
@@ -445,69 +464,7 @@ function AuthFormInner({
445
464
  if (mode === "SIGNUP" && signupStep === "NAME") handleNextSignupStep();
446
465
  else if (mode === "SIGNUP" && signupStep === "ORGANIZATION") handleNextSignupStep();
447
466
  else handleAuthRequestSubmit();
448
- } }, mode === "SIGNUP" && signupStep === "NAME" && requireNames && /* @__PURE__ */ React5.createElement("div", { className: "flex flex-col gap-6" }, /* @__PURE__ */ React5.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React5.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "First Name"), /* @__PURE__ */ React5.createElement(
449
- "input",
450
- {
451
- type: "text",
452
- value: firstName,
453
- onChange: (e) => setFirstName(cleanAlpha(e.target.value)),
454
- required: true,
455
- autoFocus: true,
456
- className: "w-full px-2 py-3 text-sm bg-transparent border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300",
457
- placeholder: "First name"
458
- }
459
- )), /* @__PURE__ */ React5.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React5.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Last Name"), /* @__PURE__ */ React5.createElement(
460
- "input",
461
- {
462
- type: "text",
463
- value: lastName,
464
- onChange: (e) => setLastName(cleanAlpha(e.target.value)),
465
- required: true,
466
- className: "w-full px-2 py-3 bg-transparent text-sm border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300",
467
- placeholder: "Last name"
468
- }
469
- ))), mode === "SIGNUP" && signupStep === "ORGANIZATION" && requireOrganization && /* @__PURE__ */ React5.createElement("div", { className: "flex flex-col gap-6" }, /* @__PURE__ */ React5.createElement("div", { className: "space-y-1.5 relative" }, /* @__PURE__ */ React5.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Organization Name"), /* @__PURE__ */ React5.createElement(
470
- "input",
471
- {
472
- type: "text",
473
- value: orgName,
474
- onChange: (e) => setOrgName(cleanOrgName(e.target.value)),
475
- required: true,
476
- autoFocus: true,
477
- className: "w-full px-2 py-3 text-sm bg-transparent border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300",
478
- placeholder: "Acme Corporation"
479
- }
480
- ))), (mode === "LOGIN" || mode === "SIGNUP" && signupStep === "EMAIL_ID") && /* @__PURE__ */ React5.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React5.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React5.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Email ID"), /* @__PURE__ */ React5.createElement(
481
- "input",
482
- {
483
- type: "email",
484
- value: emailId,
485
- onChange: (e) => setEmailId(cleanEmailId(e.target.value)),
486
- required: true,
487
- autoFocus: true,
488
- className: "w-full px-2 py-3 bg-transparent text-sm border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300",
489
- placeholder: "name@company.com"
490
- }
491
- )), mode === "SIGNUP" && /* @__PURE__ */ React5.createElement("div", { className: "flex items-start gap-3 mt-4" }, /* @__PURE__ */ React5.createElement(
492
- "input",
493
- {
494
- type: "checkbox",
495
- id: "zairus-terms",
496
- checked: agreedToTerms,
497
- onChange: (e) => setAgreedToTerms(e.target.checked),
498
- className: "mt-0.5 w-4 h-4 bg-white border-neutral-300 rounded text-black focus:ring-black cursor-pointer",
499
- required: true
500
- }
501
- ), /* @__PURE__ */ React5.createElement("label", { htmlFor: "zairus-terms", className: "text-[11px] text-neutral-500 cursor-pointer leading-snug" }, "I agree to ", companyName, "'s ", /* @__PURE__ */ React5.createElement("a", { href: termsUrl, target: "_blank", rel: "noreferrer", className: "text-black underline " }, "Terms of Service"), " and ", /* @__PURE__ */ React5.createElement("a", { href: privacyUrl, target: "_blank", rel: "noreferrer", className: "text-black underline " }, "Privacy Policy"), "."))), /* @__PURE__ */ React5.createElement(
502
- ThreeDActionButton,
503
- {
504
- type: "submit",
505
- disabled: isContinueDisabled(),
506
- isLoading: isSubmitting,
507
- className: "w-full mt-10"
508
- },
509
- "Continue"
510
- ))), step === "OTP" && /* @__PURE__ */ React5.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ React5.createElement("div", { className: "text-center mb-10 mt-2" }, /* @__PURE__ */ React5.createElement("h2", { className: " text-xl text-black mb-2 tracking-tight " }, "Security Check"), /* @__PURE__ */ React5.createElement("p", { className: "text-[13px] text-neutral-500" }, "Enter the code sent to ", /* @__PURE__ */ React5.createElement("br", null), /* @__PURE__ */ React5.createElement("span", { className: "text-black " }, emailId))), /* @__PURE__ */ React5.createElement("form", { className: "space-y-10", autoComplete: "off", onSubmit: (e) => {
467
+ } }, mode === "SIGNUP" && signupStep === "NAME" && requireNames && /* @__PURE__ */ React5.createElement("div", { className: "flex flex-col gap-6" }, /* @__PURE__ */ React5.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React5.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "First Name"), /* @__PURE__ */ React5.createElement("input", { type: "text", value: firstName, onChange: (e) => setFirstName(cleanAlpha(e.target.value)), required: true, autoFocus: true, className: "w-full px-2 py-3 text-sm bg-transparent border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300", placeholder: "First name" })), /* @__PURE__ */ React5.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React5.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Last Name"), /* @__PURE__ */ React5.createElement("input", { type: "text", value: lastName, onChange: (e) => setLastName(cleanAlpha(e.target.value)), required: true, className: "w-full px-2 py-3 bg-transparent text-sm border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300", placeholder: "Last name" }))), mode === "SIGNUP" && signupStep === "ORGANIZATION" && requireOrganization && /* @__PURE__ */ React5.createElement("div", { className: "flex flex-col gap-6" }, /* @__PURE__ */ React5.createElement("div", { className: "space-y-1.5 relative" }, /* @__PURE__ */ React5.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Organization Name"), /* @__PURE__ */ React5.createElement("input", { type: "text", value: orgName, onChange: (e) => setOrgName(cleanOrgName(e.target.value)), required: true, autoFocus: true, className: "w-full px-2 py-3 text-sm bg-transparent border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300", placeholder: "Acme Corporation" }))), (mode === "LOGIN" || mode === "SIGNUP" && signupStep === "EMAIL_ID") && /* @__PURE__ */ React5.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React5.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React5.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Email ID"), /* @__PURE__ */ React5.createElement("input", { type: "email", value: emailId, onChange: (e) => setEmailId(cleanEmailId(e.target.value)), required: true, autoFocus: true, className: "w-full px-2 py-3 bg-transparent text-sm border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300", placeholder: "name@company.com" })), mode === "SIGNUP" && /* @__PURE__ */ React5.createElement("div", { className: "flex items-start gap-3 mt-4" }, /* @__PURE__ */ React5.createElement("input", { type: "checkbox", id: "zairus-terms", checked: agreedToTerms, onChange: (e) => setAgreedToTerms(e.target.checked), className: "mt-0.5 w-4 h-4 bg-white border-neutral-300 rounded text-black focus:ring-black cursor-pointer", required: true }), /* @__PURE__ */ React5.createElement("label", { htmlFor: "zairus-terms", className: "text-[11px] text-neutral-500 cursor-pointer leading-snug" }, "I agree to ", companyName, "'s ", /* @__PURE__ */ React5.createElement("a", { href: termsUrl, target: "_blank", rel: "noreferrer", className: "text-black underline " }, "Terms of Service"), " and ", /* @__PURE__ */ React5.createElement("a", { href: privacyUrl, target: "_blank", rel: "noreferrer", className: "text-black underline " }, "Privacy Policy"), "."))), /* @__PURE__ */ React5.createElement(ThreeDActionButton, { type: "submit", disabled: isContinueDisabled(), isLoading: isSubmitting, className: "w-full mt-10" }, "Continue"))), step === "OTP" && /* @__PURE__ */ React5.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ React5.createElement("div", { className: "text-center mb-10 mt-2" }, /* @__PURE__ */ React5.createElement("h2", { className: "text-xl text-black mb-2 tracking-tight" }, "Security Check"), /* @__PURE__ */ React5.createElement("p", { className: "text-[13px] text-neutral-500" }, userHas2FA ? "Enter the code from your authenticator app." : `Enter the code sent to ${emailId}`)), /* @__PURE__ */ React5.createElement("form", { className: "space-y-8", autoComplete: "off", onSubmit: (e) => {
511
468
  e.preventDefault();
512
469
  verifyOtpCode(otp.join(""));
513
470
  } }, /* @__PURE__ */ React5.createElement("div", { className: "flex justify-between gap-2", onPaste: handlePaste }, otp.map((digit, index) => /* @__PURE__ */ React5.createElement(
@@ -522,17 +479,19 @@ function AuthFormInner({
522
479
  maxLength: 1,
523
480
  value: digit,
524
481
  onChange: (e) => handleOtpChange(index, e.target.value),
525
- className: "w-8 h-8 text-center text-lg bg-transparent border-b-2 border-neutral-100 text-black outline-none focus:border-black transition-all duration-300"
482
+ onKeyDown: (e) => {
483
+ if (e.key === "Backspace" && !otp[index] && index > 0) inputRefs.current[index - 1]?.focus();
484
+ },
485
+ className: "w-10 h-12 text-center text-lg bg-transparent border-b-2 border-neutral-100 text-black outline-none focus:border-black transition-all duration-300"
526
486
  }
527
- ))), /* @__PURE__ */ React5.createElement(
528
- ThreeDActionButton,
487
+ ))), /* @__PURE__ */ React5.createElement(ThreeDActionButton, { type: "submit", disabled: otp.join("").length < 6, isLoading: isSubmitting, className: "w-full" }, "Verify Code")), userHasPasskey && /* @__PURE__ */ React5.createElement("div", { className: "mt-8 pt-6 " }, /* @__PURE__ */ React5.createElement(
488
+ "button",
529
489
  {
530
- type: "submit",
531
- disabled: otp.join("").length < 6,
532
- isLoading: isSubmitting,
533
- className: "w-full"
490
+ onClick: triggerPasskeyLogin,
491
+ disabled: isSubmitting,
492
+ className: "w-full py-3 rounded-full text-black bg-neutral-100 hover:bg-neutral-200 text-[13px] font-medium tracking-wide transition-colors outline-none disabled:opacity-50"
534
493
  },
535
- "Verify Code"
494
+ "Use Passkey / Biometrics"
536
495
  ))))));
537
496
  }
538
497
  var PipleAuth = (props) => {
@@ -2849,7 +2808,15 @@ var UniversalSecurityPage = ({
2849
2808
  toggleLoading: isPasskeyLoading,
2850
2809
  isLast: true
2851
2810
  }
2852
- ))), show2FASetupModal && /* @__PURE__ */ React27.createElement("div", { className: "fixed inset-0 z-120 flex items-center justify-center p-4 pointer-events-auto" }, /* @__PURE__ */ React27.createElement("div", { className: "absolute inset-0 bg-black/40 ", onClick: () => !isSubmitting && setShow2FASetupModal(false) }), /* @__PURE__ */ React27.createElement("div", { className: "relative w-full max-w-sm bg-white rounded-2xl flex flex-col overflow-hidden shadow-2xl animate-in zoom-in-95 duration-200 max-h-[90vh]" }, /* @__PURE__ */ React27.createElement("div", { className: "flex items-center justify-between p-5 shrink-0 " }, /* @__PURE__ */ React27.createElement("h3", { className: "text-[15px] text-black tracking-tight" }, "Enable 2FA"), /* @__PURE__ */ React27.createElement("button", { onClick: () => !isSubmitting && setShow2FASetupModal(false), className: "text-neutral-400 hover:text-black transition-colors outline-none" }, /* @__PURE__ */ React27.createElement(HugeiconsIcon19, { icon: CancelCircleIcon6, size: 18 }))), /* @__PURE__ */ React27.createElement("div", { className: "flex-1 overflow-y-auto custom-scrollbar p-6 pt-2" }, /* @__PURE__ */ React27.createElement("div", { className: "w-full flex justify-center mb-6 mix-blend-multiply" }, twoFaQr ? /* @__PURE__ */ React27.createElement("img", { src: twoFaQr, alt: "2FA QR Code", className: "w-40 h-40 object-contain rounded-xl border border-neutral-100 p-2" }) : /* @__PURE__ */ React27.createElement("div", { className: "w-40 h-40 bg-neutral-100 rounded-xl flex items-center justify-center animate-pulse" })), /* @__PURE__ */ React27.createElement("div", { className: "text-center mb-8 mt-2" }, /* @__PURE__ */ React27.createElement("h2", { className: "text-xl text-black mb-2 tracking-tight" }, "Security Check"), /* @__PURE__ */ React27.createElement("p", { className: "text-[13px] text-neutral-500" }, "Enter the 6-digit code from your app")), /* @__PURE__ */ React27.createElement("form", { className: "space-y-4", autoComplete: "off", onSubmit: (e) => {
2811
+ ))), show2FASetupModal && /* @__PURE__ */ React27.createElement("div", { className: "fixed inset-0 z-120 flex items-center justify-center p-4 pointer-events-auto" }, /* @__PURE__ */ React27.createElement("div", { className: "absolute inset-0 bg-black/40 ", onClick: () => !isSubmitting && setShow2FASetupModal(false) }), /* @__PURE__ */ React27.createElement("div", { className: "relative w-full max-w-sm bg-white rounded-2xl flex flex-col overflow-hidden shadow-2xl animate-in zoom-in-95 duration-200 max-h-[90vh]" }, /* @__PURE__ */ React27.createElement("div", { className: "flex items-center justify-between p-5 shrink-0 " }, /* @__PURE__ */ React27.createElement("h3", { className: "text-[15px] text-black tracking-tight" }, "Enable 2FA"), /* @__PURE__ */ React27.createElement("button", { onClick: () => !isSubmitting && setShow2FASetupModal(false), className: "text-neutral-400 hover:text-black transition-colors outline-none" }, /* @__PURE__ */ React27.createElement(HugeiconsIcon19, { icon: CancelCircleIcon6, size: 18 }))), /* @__PURE__ */ React27.createElement("div", { className: "flex-1 overflow-y-auto custom-scrollbar p-6 pt-2" }, /* @__PURE__ */ React27.createElement("div", { className: "w-full flex justify-center mb-6 mix-blend-multiply" }, twoFaQr ? /* @__PURE__ */ React27.createElement("img", { src: twoFaQr, alt: "2FA QR Code", className: "w-40 h-40 object-contain rounded-xl border border-neutral-100 p-2" }) : /* @__PURE__ */ React27.createElement("div", { className: "w-40 h-40 bg-neutral-100 rounded-xl flex items-center justify-center animate-pulse" })), /* @__PURE__ */ React27.createElement("div", { className: "grid grid-cols-1 gap-y-6 gap-x-4 mb-8" }, /* @__PURE__ */ React27.createElement("div", null, /* @__PURE__ */ React27.createElement("span", { className: "text-[11px] tracking-[0.2em] text-neutral-400 block mb-2 uppercase" }, "Setup Key"), /* @__PURE__ */ React27.createElement("div", { className: "flex items-center gap-2 min-w-0 bg-neutral-50 px-3 py-2.5 rounded-xl " }, /* @__PURE__ */ React27.createElement("span", { className: "text-[13px] font-mono tracking-widest text-black truncate min-w-0 block flex-1" }, twoFaSecret), /* @__PURE__ */ React27.createElement(
2812
+ "button",
2813
+ {
2814
+ type: "button",
2815
+ onClick: () => handleCopySecret(twoFaSecret),
2816
+ className: "text-neutral-400 hover:text-black transition-colors outline-none shrink-0 ml-2"
2817
+ },
2818
+ /* @__PURE__ */ React27.createElement(HugeiconsIcon19, { icon: Copy01Icon3, size: 16 })
2819
+ )))), /* @__PURE__ */ React27.createElement("div", { className: "text-center mb-8 mt-2" }, /* @__PURE__ */ React27.createElement("h2", { className: "text-xl text-black mb-2 tracking-tight" }, "Security Check"), /* @__PURE__ */ React27.createElement("p", { className: "text-[13px] text-neutral-500" }, "Enter the 6-digit code from your app")), /* @__PURE__ */ React27.createElement("form", { className: "space-y-4", autoComplete: "off", onSubmit: (e) => {
2853
2820
  e.preventDefault();
2854
2821
  submitEnable2FA(otp.join(""));
2855
2822
  } }, /* @__PURE__ */ React27.createElement("div", { className: "flex justify-between gap-2 pb-6", onPaste: handlePaste }, otp.map((digit, index) => /* @__PURE__ */ React27.createElement(
@@ -2920,7 +2887,7 @@ var UniversalSecurityPage = ({
2920
2887
  className: "w-full py-3.5"
2921
2888
  },
2922
2889
  "Verify & Disable"
2923
- ))))), showPasskeyModal && /* @__PURE__ */ React27.createElement("div", { className: "fixed inset-0 z-120 flex items-center justify-center p-4 pointer-events-auto" }, /* @__PURE__ */ React27.createElement("div", { className: "absolute inset-0 bg-black/40 ", onClick: () => !isSubmitting && setShowPasskeyModal(false) }), /* @__PURE__ */ React27.createElement("div", { className: "relative w-full max-w-sm bg-white rounded-2xl flex flex-col overflow-hidden shadow-2xl animate-in zoom-in-95 duration-200" }, /* @__PURE__ */ React27.createElement("div", { className: "flex items-center justify-between p-5 shrink-0" }, /* @__PURE__ */ React27.createElement("h3", { className: "text-[15px] text-black tracking-tight" }, "Enable Passkey"), /* @__PURE__ */ React27.createElement("button", { onClick: () => !isSubmitting && setShowPasskeyModal(false), className: "text-neutral-400 hover:text-black transition-colors outline-none" }, /* @__PURE__ */ React27.createElement(HugeiconsIcon19, { icon: CancelCircleIcon6, size: 18 }))), /* @__PURE__ */ React27.createElement("div", { className: "p-6 text-center" }, /* @__PURE__ */ React27.createElement("p", { className: "text-[13px] text-neutral-500 mb-8 leading-relaxed" }, "Use your device's biometrics (Face ID, Touch ID, or PIN) to securely sign into your account without needing a password."), /* @__PURE__ */ React27.createElement(
2890
+ ))))), showPasskeyModal && /* @__PURE__ */ React27.createElement("div", { className: "fixed inset-0 z-120 flex items-center justify-center p-4 pointer-events-auto" }, /* @__PURE__ */ React27.createElement("div", { className: "absolute inset-0 bg-black/40 ", onClick: () => !isSubmitting && setShowPasskeyModal(false) }), /* @__PURE__ */ React27.createElement("div", { className: "relative w-full max-w-sm bg-white rounded-2xl flex flex-col overflow-hidden shadow-2xl animate-in zoom-in-95 duration-200" }, /* @__PURE__ */ React27.createElement("div", { className: "flex items-center justify-between p-5 shrink-0" }, /* @__PURE__ */ React27.createElement("h3", { className: "text-[15px] text-black tracking-tight" }, "Enable Passkey"), /* @__PURE__ */ React27.createElement("button", { onClick: () => !isSubmitting && setShowPasskeyModal(false), className: "text-neutral-400 hover:text-black transition-colors outline-none" }, /* @__PURE__ */ React27.createElement(HugeiconsIcon19, { icon: CancelCircleIcon6, size: 18 }))), /* @__PURE__ */ React27.createElement("div", { className: "p-6 text-center" }, /* @__PURE__ */ React27.createElement("h2", { className: "text-lg text-black mb-2 tracking-tight" }, "Simplify your Sign In"), /* @__PURE__ */ React27.createElement("p", { className: "text-[13px] text-neutral-500 mb-8 leading-relaxed" }, "Use your device's biometrics (Face ID, Touch ID, or PIN) to securely sign into your account without needing a password."), /* @__PURE__ */ React27.createElement(
2924
2891
  ThreeDActionButton,
2925
2892
  {
2926
2893
  onClick: submitEnablePasskey,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retinalabsllc/zairusjs",
3
- "version": "11.0.1",
3
+ "version": "11.0.2",
4
4
  "description": "A perceptive, Ai data driven Next.js UI component library.",
5
5
  "author": "Retina Labs Company",
6
6
  "license": "MIT",